feat: add Undici adapter (follow-up) by niksy · Pull Request #10893 · axios/axios · GitHub
Skip to content

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

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

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

Conversation

@niksy

@niksy niksy commented May 14, 2026

Copy link
Copy Markdown

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.

Things currently not fleshed out:

  • Documentation regarding usage
    • It’s on developers to install Undici as package, so we should probably throw if Undici is not available
  • Which versions of Undici are we supporting
    • I’m voting for latest and anything not compatible throws error
  • Types for request options
    • undici.fetch and undici.request implementation have different options so maybe we should pick subset of common options (currently fetchOptions is used)
  • Specification compliance section
    • Specifically part regarding GC and response body consumption
  • Errors
    • undici.fetch and undici.request have different errors for same requests (fetch needs to follow Fetch specification), so I’ve tried to remove differences by discarding Fetch-specific logic regarding network error
    • Is there anything here that needs to be carefully considered?

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 preserves Axios behavior for redirects, timeouts, progress, streaming, size limits, and FormData. It lazy-loads undici, uses undici.request by default, and switches to undici.fetch only when needed.

Description

  • Summary of changes
    • Added lib/adapters/undici.js and registered it as 'undici'; updated README and types.
    • Lazy-imports undici with a clear resolver error; adds optional peer dep undici@^8 (Node >= 22.19) and dev dep.
    • Defaults to undici.request; uses undici.fetch only for progress, maxContentLength, or responseType stream/response/formdata/blob.
    • FormData: converts request FormData to environment/undici.FormData; bridges response.formData() to native FormData; also normalizes request FormData in the fetch adapter.
    • Redirects: enforces maxRedirects via Undici’s global dispatcher + redirect interceptor; normalizes to AxiosError.ERR_FR_TOO_MANY_REDIRECTS in both code paths.
    • Errors/timeouts: normalizes timeouts to ETIMEDOUT; maps ENOTFOUND to AxiosError: Network Error with cause.
    • Limits/headers: enforces maxBodyLength/maxContentLength (incl. chunked and data: URLs); sanitizes headers; sets default User-Agent to axios/<version> unless provided.
  • Reasoning
    • Use undici efficiently in Node while preserving Axios semantics.
  • Additional context

Docs

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

Testing

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

  • Response types (text/json/arraybuffer/blob/stream/formdata), params, QUERY with body, header sanitization (incl. Unicode).
  • FormData: auto Content-Type, removing manual header without boundary, preserving custom boundary, and request conversion to env/undici.FormData.
  • Abort vs timeout behavior (timeouts normalized to ETIMEDOUT); cancel classification.
  • Size limits for maxBodyLength/maxContentLength (incl. chunked and data: URLs).
  • Redirect handling via global dispatcher with consistent ERR_FR_TOO_MANY_REDIRECTS across request and fetch.
  • Expanded basic auth cases: URL credentials decoding, UTF‑8 encoding, malformed credentials preserved, password‑only, config auth preferred over URL, and header precedence.

Semantic version impact

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

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

Review in cubic

@socket-security

socket-security Bot commented May 14, 2026

Copy link
Copy Markdown

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

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.

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 Outdated
Comment thread lib/adapters/undici.js
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
Comment thread tests/unit/adapters/undici.test.js
Comment thread tests/unit/adapters/undici.test.js
Comment thread tests/unit/adapters/undici.test.js
Comment thread tests/unit/adapters/undici.test.js
Comment thread lib/adapters/undici.js
@niksy

niksy commented May 14, 2026

Copy link
Copy Markdown
Author

@cubic-dev-ai

@cubic-dev-ai

cubic-dev-ai Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai

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

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

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.

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

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

niksy commented May 16, 2026

Copy link
Copy Markdown
Author

@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.

Comment thread lib/adapters/fetch.js Outdated
let response = await (isRequestSupported
? _fetch(request, fetchOptions)
: _fetch(url, resolvedOptions));
? _fetch(request, fetchOptions, resolvedConfig)

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.

Since Undici adapter has different behavior based on passed config options (fast undici.request for most use cases, undici.fetch for stream/FormData, …) we pass currently resolved config to custom fetch adapter getter.

We have that config available in adapter, but that also means we create additional fetch adapter seed cache map entries for each Axios request which seems unecessary and uses more memory just because we have different usage semantics which could be inferred in run time of fetch function instead of Fetch adapter getter.

@niksy niksy May 17, 2026

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.

@jasonsaayman I’ve reverted this change since I’m not sure if this is the correct approach.

Can you provide some pointers here? Am I using getFetch correctly?

It does seem like a waste to create new seed entry for each Axios request if we can manage to resolve everything when running env.fetch function and third argument which is final resolved config.

Comment thread lib/adapters/undici.js

@niksy niksy May 17, 2026

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.

Maybe we should see if we really need to use Undici globals. Are there any drawbacks if we use globals exposed via current Node fetch implementation (other than newer and possible more optimized implementations)? Maybe someone from Node can chime in with pros/cons of this approach.

@jasonsaayman jasonsaayman added commit::feat The PR is related to a feature status::blocked This PR or issue is deemed to be blocked and removed priority::medium labels Jun 14, 2026
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 status::blocked This PR or issue is deemed to be blocked

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants