Skip to content

feat: add Undici adapter (follow-up)#10893

Open
niksy wants to merge 6 commits into
axios:v1.xfrom
niksy:undici-v2
Open

feat: add Undici adapter (follow-up)#10893
niksy wants to merge 6 commits into
axios:v1.xfrom
niksy:undici-v2

Conversation

@niksy
Copy link
Copy Markdown

@niksy niksy commented May 14, 2026

Summary

This PR is a follow-up to #6915, with up-to-date changes and minimal implementation using custom Fetch adapter.

Test suite is adapted from original PR and adjusted with latest fetch test suite changes, but there are 3 tests that are currently failing. I’ve marked them with comments so we can easily see and discuss potential changes.

This is the content diff between two test suites: https://www.diffchecker.com/hXpATVrd/

I’ve tried to omit what isn’t necessary for Undici test, but I don’t know if I missed something important.

Linked issue

Issue #10885 tracks all the necessary changes.

Checklist

  • Tests added or updated (or N/A with reason)
  • Docs / types updated if public API changed (index.d.ts and index.d.cts)
  • No breaking changes (or called out explicitly above)

Summary by cubic

Adds a Node-only undici adapter that aligns Axios behavior with Undici’s request/fetch pipeline for redirects, timeouts, progress, streaming, and size limits. Redirects are enforced via Undici’s global dispatcher with consistent error mapping across both undici.request and undici.fetch.

Description

  • Added lib/adapters/undici.js, registered it as undici in lib/adapters/adapters.js; README and types include 'undici'.
  • Lazy-imports undici; Node-only by aliasing to ./lib/helpers/null.js in browser/RN builds.
  • Defaults to undici.request; switches to undici.fetch only when needed (progress, maxContentLength, or responseType: stream/response/formdata/blob).
  • Redirects: uses Undici’s global dispatcher + redirect interceptor to enforce maxRedirects; normalizes “too many redirects” to AxiosError.ERR_FR_TOO_MANY_REDIRECTS for both paths.
  • Errors and timeouts: preserves Axios abort/timeout semantics; normalizes timeouts to ETIMEDOUT; maps DNS ENOTFOUND to AxiosError: Network Error with cause.
  • Limits and headers: enforces maxBodyLength/maxContentLength (including chunked and data: URLs); sanitizes headers; auto-sets FormData Content-Type; sets default User-Agent to axios/<version> without overriding user-provided values.
  • Peer dep: optional undici@^8 (Node >= 22.19); added as dev dep for tests.
  • Tracks requirements in Undici adapter: pick up and finish PR #6915 #10885.

Docs

Add a new /docs/ page for the undici adapter covering: install (npm i undici), usage (adapter: 'undici'), Node-only scope and minimum Node version, lazy import behavior, when request vs fetch is used, redirect/timeout normalization, progress/streaming, size limits, and default/overrideable User-Agent.

Testing

Added tests/unit/adapters/undici.test.js:

  • Response types (text/json/arraybuffer/blob/stream/formdata); upload/download progress; params and QUERY with body; basic auth; header sanitization; FormData Content-Type; default/custom User-Agent.
  • Aborts and timeout normalization (ETIMEDOUT) and cancel vs timeout classification.
  • Size limits for maxBodyLength, maxContentLength (including chunked and data: URLs).
  • Redirects: verify maxRedirects via Undici’s global dispatcher interceptor and consistent ERR_FR_TOO_MANY_REDIRECTS mapping for the fetch path.

Semantic version impact

Minor: adds a new adapter and type literal ('undici') without breaking existing APIs.

Written for commit f14bbf2. Summary will update on new commits.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented May 14, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​undici@​8.2.07210010097100

View full report

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 8 files

Confidence score: 2/5

  • There is a high-confidence runtime risk in package.json: declaring undici as optional conflicts with static imports in the adapter path, which can trigger module resolution failures in Node.js at runtime.
  • lib/adapters/undici.js has concrete behavior regressions: basic auth URLs can fail with undici Request, and the mixed static/dynamic loading plus per-request new Agent() adds both correctness and performance risk.
  • Test reliability is also shaky in tests/unit/adapters/undici.test.js because undefined helper references can cause ReferenceError before adapter logic is exercised, reducing confidence in coverage of these changes.
  • Pay close attention to package.json, lib/adapters/undici.js, tests/unit/adapters/undici.test.js - runtime dependency resolution, auth handling, and test validity need fixes before merge.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/adapters/undici.js">

<violation number="1" location="lib/adapters/undici.js:2">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**

Contradictory static and dynamic `undici` loading creates impossible fallback logic. The file static-imports `{ Agent, interceptors } from 'undici'` at module load time, so the module will already fail to load if `undici` is unavailable. Despite this, a runtime `try/catch` around `await import('undici')` assigns `undici = null` on failure, but the code then unconditionally accesses `undici.fetch`, `undici.request`, `undici.Request`, and `undici.Response` — making the null fallback non-functional and unreachable. This is unnecessary defensive handling for an impossible edge case.</violation>

<violation number="2" location="lib/adapters/undici.js:33">
P2: Per-request `new Agent()` causes avoidable connection pool churn and resource overhead</violation>

<violation number="3" location="lib/adapters/undici.js:105">
P1: Basic auth fails because undici `Request` rejects URLs containing credentials. The adapter should sanitize URL credentials before request construction.</violation>
</file>

<file name="tests/unit/adapters/undici.test.js">

<violation number="1" location="tests/unit/adapters/undici.test.js:589">
P2: Abort-normalization test references helpers that are not defined or imported in this file, so it will fail with ReferenceError before exercising the adapter.</violation>

<violation number="2" location="tests/unit/adapters/undici.test.js:686">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**

The test claims to validate AxiosError/ERR_NETWORK handling, but it only checks the nested cause code and never verifies the Axios error shape it names.</violation>
</file>

<file name="package.json">

<violation number="1" location="package.json:53">
P2: Missing `exports` entry for new undici adapter subpath</violation>

<violation number="2" location="package.json:170">
P0: Optional peer dependency `undici` will cause runtime module resolution failures in Node.js because `lib/adapters/undici.js` statically imports `undici` at the top level, and `lib/adapters/adapters.js` statically imports the undici adapter module. This means any Node.js user importing axios without `undici` installed will get `Cannot find module 'undici'` at startup.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread package.json
"selfsigned": "^5.5.0",
"stream-throttle": "^0.1.3",
"typescript": "^5.9.3",
"undici": "^8.2.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Optional peer dependency undici will cause runtime module resolution failures in Node.js because lib/adapters/undici.js statically imports undici at the top level, and lib/adapters/adapters.js statically imports the undici adapter module. This means any Node.js user importing axios without undici installed will get Cannot find module 'undici' at startup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 170:

<comment>Optional peer dependency `undici` will cause runtime module resolution failures in Node.js because `lib/adapters/undici.js` statically imports `undici` at the top level, and `lib/adapters/adapters.js` statically imports the undici adapter module. This means any Node.js user importing axios without `undici` installed will get `Cannot find module 'undici'` at startup.</comment>

<file context>
@@ -165,8 +167,17 @@
     "selfsigned": "^5.5.0",
     "stream-throttle": "^0.1.3",
     "typescript": "^5.9.3",
+    "undici": "^8.2.0",
     "vitest": "^4.1.5"
   },
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed!

Comment thread lib/adapters/undici.js
formData: () => asResponse().formData()
};
},
Request: undici.Request,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Basic auth fails because undici Request rejects URLs containing credentials. The adapter should sanitize URL credentials before request construction.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/adapters/undici.js, line 105:

<comment>Basic auth fails because undici `Request` rejects URLs containing credentials. The adapter should sanitize URL credentials before request construction.</comment>

<file context>
@@ -0,0 +1,114 @@
+          formData: () => asResponse().formData()
+        };
+      },
+      Request: undici.Request,
+      Response: undici.Response
+    }
</file context>

Comment thread lib/adapters/undici.js Outdated
Comment thread tests/unit/adapters/undici.test.js
Comment thread lib/adapters/undici.js Outdated
Comment thread package.json
@@ -50,12 +50,14 @@
"browser": {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Missing exports entry for new undici adapter subpath

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At package.json, line 53:

<comment>Missing `exports` entry for new undici adapter subpath</comment>

<file context>
@@ -50,12 +50,14 @@
   "browser": {
     "./dist/node/axios.cjs": "./dist/browser/axios.cjs",
     "./lib/adapters/http.js": "./lib/helpers/null.js",
+    "./lib/adapters/undici.js": "./lib/helpers/null.js",
     "./lib/platform/node/index.js": "./lib/platform/browser/index.js",
     "./lib/platform/node/classes/FormData.js": "./lib/helpers/null.js"
</file context>

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t understand the issue here? This should be nullable since it’s not available in browser?


it('should not classify a user-initiated abort as a timeout', async () => {
const safariFetch = (url, init) => {
const signal = getFetchSignal(url, init);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Abort-normalization test references helpers that are not defined or imported in this file, so it will fail with ReferenceError before exercising the adapter.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/adapters/undici.test.js, line 589:

<comment>Abort-normalization test references helpers that are not defined or imported in this file, so it will fail with ReferenceError before exercising the adapter.</comment>

<file context>
@@ -0,0 +1,1034 @@
+
+    it('should not classify a user-initiated abort as a timeout', async () => {
+      const safariFetch = (url, init) => {
+        const signal = getFetchSignal(url, init);
+
+        return new Promise((_resolve, reject) => {
</file context>

}
});

it('should remove manually set Content-Type without boundary for FormData', async () => {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one fails, currently don’t know why

}
});

it('should preserve Content-Type if it already has boundary', async () => {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one fails, currently don’t know why

});
});

it('should support basic auth', async () => {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one fails probably because Request is initialized with auth information which is forbidden. Error is TypeError: Request cannot be constructed from a URL that includes credentials: http://foo@localhost:8010/

This should be probbably be handled at adapter level to omit that information and pass it as request initialization options.

I’m curious why this doesn’t fail in fetch adapter test with native Request.

Copy link
Copy Markdown
Author

@niksy niksy May 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually tested wrong infetch adapter test suite: https://github.com/axios/axios/blob/v1.x/tests/unit/adapters/fetch.test.js#L468

It uses default Axios instead instead of Fetch adapter instance so it always passes.

I went ahead and created PR with adjusted test and new tests based on http adapter and URL parsing for fetch adapter case. This should also fix Undici test.

Comment thread lib/adapters/undici.js
formData: () => asResponse().formData()
};
},
Request: undici.Request,
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we could squeeze even more performance by avoiding creating Request/Response instances for undici.request path since it produces basic response?

@niksy
Copy link
Copy Markdown
Author

niksy commented May 14, 2026

@cubic-dev-ai

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented May 14, 2026

@cubic-dev-ai

@niksy I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 8 files

Confidence score: 3/5

  • There is a concrete regression risk in lib/adapters/adapters.js: registering undici as a direct adapter function instead of a lazy resolver object can change adapter fallback behavior at runtime.
  • Given the medium severity (6/10) and solid confidence (7/10), this is more than a cosmetic issue and could affect users depending on fallback adapter selection.
  • Pay close attention to lib/adapters/adapters.js - ensure undici follows the same lazy resolver pattern as fetch so fallback semantics remain intact.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="lib/adapters/adapters.js">

<violation number="1" location="lib/adapters/adapters.js:23">
P2: `undici` is registered as a direct adapter function instead of a lazy resolver object like `fetch`, breaking adapter fallback semantics</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread lib/adapters/adapters.js
fetch: {
get: fetchAdapter.getFetch,
},
undici: undiciAdapter
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: undici is registered as a direct adapter function instead of a lazy resolver object like fetch, breaking adapter fallback semantics

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/adapters/adapters.js, line 23:

<comment>`undici` is registered as a direct adapter function instead of a lazy resolver object like `fetch`, breaking adapter fallback semantics</comment>

<file context>
@@ -19,6 +20,7 @@ const knownAdapters = {
   fetch: {
     get: fetchAdapter.getFetch,
   },
+  undici: undiciAdapter
 };
 
</file context>
Suggested change
undici: undiciAdapter
undici: {
get: (config) => undiciAdapter.get?.(config) ?? undiciAdapter
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this relevant? Undici adapter uses Fetch adapter getFetch method underneath.

i = 1;
}

// option is ignored in undici.fetch
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not happy about this. It basically means we have different behavior based on requested response type. How should we handle this?

@jasonsaayman jasonsaayman added priority::medium A medium priority commit::feat The PR is related to a feature labels May 14, 2026
@jasonsaayman
Copy link
Copy Markdown
Member

@niksy can you please look at resolving the comments if they have been fixed or leave a comment as to why you disagree

@niksy
Copy link
Copy Markdown
Author

niksy commented May 16, 2026

@jasonsaayman will do!

I’m running same benchmark as in original PR (https://github.com/nodejs/undici/tree/main/benchmarks), but unfortunately I’m not seeing any of the large gains from undici.request. I was wondering if it’s something related to Axios internals, but the largest blocker seems to be actual Undici request, but that same request when using only Undici request gives actual results.

Should we also take a look at this before merging this PR? Does it make sense to contact someone from Undici to see if there is something we could do to improve our implementation?

@jasonsaayman
Copy link
Copy Markdown
Member

@niksy I will see if I can get some eyes on this from a Node maintainer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commit::feat The PR is related to a feature priority::medium A medium priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants