Skip to content

Commit 2c27f44

Browse files
mcollinaclaude
andcommitted
feat: throw error when maxRedirections is used with undici.request()
The maxRedirections option was removed in v7 but no error was thrown when users tried to use it, making migration confusing. Now throws a clear error message directing users to use the redirect interceptor instead. Fixes #4310 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: Matteo Collina <hello@matteocollina.com>
1 parent e6ffcd8 commit 2c27f44

3 files changed

Lines changed: 130 additions & 1 deletion

File tree

CLAUDE.md

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Key Commands
6+
7+
**Testing:**
8+
- `npm run test` - Full test suite (JavaScript + TypeScript)
9+
- `npm run test:unit` - Core unit tests
10+
- `npm run test:fetch` - Fetch API tests
11+
- `npm run test:websocket` - WebSocket tests
12+
- `npm run test:h2` - HTTP/2 tests
13+
- `npm run test:cache` - Cache-related tests
14+
- `npm run test:interceptors` - Interceptor tests
15+
- `npm run test:wpt` - Web Platform Tests
16+
- `npm run test:node-test` - Node.js specific tests
17+
18+
**Single test runs:**
19+
- `borp -p "test/specific-test.js"` - Run a specific test file
20+
- `borp -p "test/category/*.js"` - Run tests in a category
21+
22+
**Linting & Quality:**
23+
- `npm run lint` - ESLint check
24+
- `npm run lint:fix` - Auto-fix linting issues
25+
- `npm run coverage` - Generate test coverage report
26+
27+
**Build:**
28+
- `npm run build:node` - Build bundled fetch for Node.js
29+
- `npm run build:wasm` - Build WebAssembly components (requires Docker)
30+
31+
## Architecture Overview
32+
33+
Undici is an HTTP/1.1 client with Fetch API implementation for Node.js, structured around several core concepts:
34+
35+
### Dispatcher Pattern
36+
The foundation is the `Dispatcher` abstract class that defines the interface for making HTTP requests. All HTTP clients implement this pattern:
37+
38+
- **Agent** - Multi-host connection pooler (default global dispatcher)
39+
- **Client** - Single-host HTTP/1.1 client with connection pooling
40+
- **Pool** - Connection pool for a single origin
41+
- **BalancedPool** - Load balancer across multiple pools
42+
- **ProxyAgent/EnvHttpProxyAgent** - Proxy-aware dispatchers
43+
- **RetryAgent** - Automatic retry logic wrapper
44+
45+
### Core Components
46+
47+
**lib/core/** - Fundamental utilities:
48+
- `connect.js` - Low-level connection establishment
49+
- `request.js` - Request object implementation
50+
- `util.js` - Parsing and validation utilities
51+
- `errors.js` - Error definitions
52+
53+
**lib/api/** - High-level API implementations:
54+
- `api-request.js` - Basic request/response
55+
- `api-stream.js` - Streaming interface
56+
- `api-pipeline.js` - Pipeline interface
57+
58+
**lib/web/** - Web standards implementations:
59+
- `fetch/` - Complete Fetch API implementation
60+
- `websocket/` - WebSocket client
61+
- `eventsource/` - EventSource implementation
62+
- `cache/` - Cache API
63+
- `cookies/` - Cookie handling
64+
65+
### HTTP Parser
66+
Uses a WebAssembly build of llhttp parser located in `lib/llhttp/`. The sources are in `deps/llhttp/` and can be rebuilt with `npm run build:wasm`.
67+
68+
### Interceptor System
69+
Interceptors wrap dispatchers to add functionality:
70+
- `redirect` - Automatic redirect handling
71+
- `retry` - Retry failed requests
72+
- `cache` - HTTP caching
73+
- `dns` - DNS resolution caching
74+
- `dump` - Request/response logging
75+
76+
### Testing Structure
77+
- `test/` - Main test directory
78+
- `test/wpt/` - Web Platform Tests
79+
- `test/fetch/` - Fetch API tests
80+
- `test/websocket/` - WebSocket tests
81+
- `test/node-test/` - Node.js integration tests
82+
- Test runner: `borp` (not Jest for most tests)
83+
84+
## Development Notes
85+
86+
**Standards Compliance:**
87+
- Implements Fetch Standard with Node.js adaptations
88+
- WebSocket per RFC 6455
89+
- HTTP/1.1 per RFC 9110
90+
- Supports HTTP/2 via h2c-client
91+
92+
**Key Files to Know:**
93+
- `index.js` - Main entry point, exports all APIs
94+
- `lib/global.js` - Global dispatcher management
95+
- `lib/dispatcher/agent.js` - Default multi-host client
96+
- `lib/web/fetch/index.js` - Fetch implementation entry
97+
- `package.json` - Contains all npm script definitions
98+
99+
**Code Style:**
100+
- Uses neostandard (ESLint config)
101+
- No trailing commas in objects/arrays
102+
- TypeScript definitions in `types/` directory
103+
104+
**Git Commits:**
105+
- Always use `git commit -s` to add signoff when committing changes
106+
107+
**WebAssembly Components:**
108+
- HTTP parser is WebAssembly-based
109+
- Requires Docker to rebuild
110+
- Pre-built binaries included in `lib/llhttp/`

lib/core/request.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class Request {
4242
reset,
4343
expectContinue,
4444
servername,
45-
throwOnError
45+
throwOnError,
46+
maxRedirections
4647
}, handler) {
4748
if (typeof path !== 'string') {
4849
throw new InvalidArgumentError('path must be a string')
@@ -86,6 +87,10 @@ class Request {
8687
throw new InvalidArgumentError('invalid throwOnError')
8788
}
8889

90+
if (maxRedirections != null) {
91+
throw new InvalidArgumentError('maxRedirections is not supported, use the redirect interceptor')
92+
}
93+
8994
this.headersTimeout = headersTimeout
9095

9196
this.bodyTimeout = bodyTimeout

test/request.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,20 @@ describe('DispatchOptions#expectContinue', () => {
177177
})
178178
})
179179

180+
describe('DispatchOptions#maxRedirections', () => {
181+
test('Should throw if maxRedirections option is used', async t => {
182+
t = tspl(t, { plan: 1 })
183+
184+
await t.rejects(request({
185+
method: 'GET',
186+
origin: 'http://somehost.xyz',
187+
maxRedirections: 5
188+
}), /maxRedirections is not supported, use the redirect interceptor/)
189+
190+
await t.completed
191+
})
192+
})
193+
180194
describe('DispatchOptions#reset', () => {
181195
test('Should throw if invalid reset option', async t => {
182196
t = tspl(t, { plan: 1 })

0 commit comments

Comments
 (0)