@npy/fetch 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +143 -50
- package/bun.lock +68 -0
- package/examples/custom-proxy-client.ts +32 -0
- package/examples/http-client.ts +47 -0
- package/examples/proxy.ts +16 -0
- package/examples/simple.ts +15 -0
- package/package.json +25 -30
- package/src/_internal/consts.ts +3 -0
- package/{_internal/decode-stream-error.d.cts → src/_internal/decode-stream-error.ts} +7 -2
- package/src/_internal/error-mapping.ts +160 -0
- package/src/_internal/guards.ts +78 -0
- package/src/_internal/net.ts +173 -0
- package/src/_internal/promises.ts +22 -0
- package/src/_internal/streams.ts +52 -0
- package/src/_internal/symbols.ts +1 -0
- package/src/agent-pool.ts +157 -0
- package/src/agent.ts +408 -0
- package/src/body.ts +179 -0
- package/src/dialers/index.ts +3 -0
- package/src/dialers/proxy.ts +102 -0
- package/src/dialers/tcp.ts +162 -0
- package/src/encoding.ts +222 -0
- package/src/errors.ts +357 -0
- package/src/fetch.ts +626 -0
- package/src/http-client.ts +111 -0
- package/src/index.ts +14 -0
- package/src/io/_utils.ts +82 -0
- package/src/io/buf-writer.ts +183 -0
- package/src/io/io.ts +322 -0
- package/src/io/readers.ts +576 -0
- package/src/io/writers.ts +331 -0
- package/{types/agent.d.cts → src/types/agent.ts} +47 -21
- package/{types/dialer.d.cts → src/types/dialer.ts} +19 -9
- package/src/types/index.ts +2 -0
- package/tests/agent-pool.test.ts +111 -0
- package/tests/agent.test.ts +134 -0
- package/tests/body.test.ts +228 -0
- package/tests/errors.test.ts +152 -0
- package/tests/fetch.test.ts +421 -0
- package/tests/io-options.test.ts +127 -0
- package/tests/multipart.test.ts +348 -0
- package/tests/test-utils.ts +335 -0
- package/tsconfig.json +15 -0
- package/LICENSE +0 -21
- package/_internal/consts.cjs +0 -4
- package/_internal/consts.d.cts +0 -3
- package/_internal/consts.d.ts +0 -3
- package/_internal/consts.js +0 -4
- package/_internal/decode-stream-error.cjs +0 -18
- package/_internal/decode-stream-error.d.ts +0 -11
- package/_internal/decode-stream-error.js +0 -18
- package/_internal/error-mapping.cjs +0 -44
- package/_internal/error-mapping.d.cts +0 -15
- package/_internal/error-mapping.d.ts +0 -15
- package/_internal/error-mapping.js +0 -41
- package/_internal/guards.cjs +0 -23
- package/_internal/guards.d.cts +0 -15
- package/_internal/guards.d.ts +0 -15
- package/_internal/guards.js +0 -15
- package/_internal/net.cjs +0 -95
- package/_internal/net.d.cts +0 -11
- package/_internal/net.d.ts +0 -11
- package/_internal/net.js +0 -92
- package/_internal/promises.cjs +0 -18
- package/_internal/promises.d.cts +0 -1
- package/_internal/promises.d.ts +0 -1
- package/_internal/promises.js +0 -18
- package/_internal/streams.cjs +0 -37
- package/_internal/streams.d.cts +0 -21
- package/_internal/streams.d.ts +0 -21
- package/_internal/streams.js +0 -36
- package/_internal/symbols.cjs +0 -4
- package/_internal/symbols.d.cts +0 -1
- package/_internal/symbols.d.ts +0 -1
- package/_internal/symbols.js +0 -4
- package/_virtual/_rolldown/runtime.cjs +0 -23
- package/agent-pool.cjs +0 -96
- package/agent-pool.d.cts +0 -2
- package/agent-pool.d.ts +0 -2
- package/agent-pool.js +0 -95
- package/agent.cjs +0 -260
- package/agent.d.cts +0 -3
- package/agent.d.ts +0 -3
- package/agent.js +0 -259
- package/body.cjs +0 -105
- package/body.d.cts +0 -12
- package/body.d.ts +0 -12
- package/body.js +0 -102
- package/dialers/index.d.cts +0 -3
- package/dialers/index.d.ts +0 -3
- package/dialers/proxy.cjs +0 -56
- package/dialers/proxy.d.cts +0 -27
- package/dialers/proxy.d.ts +0 -27
- package/dialers/proxy.js +0 -55
- package/dialers/tcp.cjs +0 -92
- package/dialers/tcp.d.cts +0 -57
- package/dialers/tcp.d.ts +0 -57
- package/dialers/tcp.js +0 -89
- package/encoding.cjs +0 -114
- package/encoding.d.cts +0 -35
- package/encoding.d.ts +0 -35
- package/encoding.js +0 -110
- package/errors.cjs +0 -275
- package/errors.d.cts +0 -110
- package/errors.d.ts +0 -110
- package/errors.js +0 -259
- package/fetch.cjs +0 -353
- package/fetch.d.cts +0 -58
- package/fetch.d.ts +0 -58
- package/fetch.js +0 -350
- package/http-client.cjs +0 -75
- package/http-client.d.cts +0 -39
- package/http-client.d.ts +0 -39
- package/http-client.js +0 -75
- package/index.cjs +0 -49
- package/index.d.cts +0 -14
- package/index.d.ts +0 -14
- package/index.js +0 -11
- package/io/_utils.cjs +0 -56
- package/io/_utils.d.cts +0 -10
- package/io/_utils.d.ts +0 -10
- package/io/_utils.js +0 -51
- package/io/buf-writer.cjs +0 -149
- package/io/buf-writer.d.cts +0 -13
- package/io/buf-writer.d.ts +0 -13
- package/io/buf-writer.js +0 -148
- package/io/io.cjs +0 -199
- package/io/io.d.cts +0 -5
- package/io/io.d.ts +0 -5
- package/io/io.js +0 -198
- package/io/readers.cjs +0 -337
- package/io/readers.d.cts +0 -69
- package/io/readers.d.ts +0 -69
- package/io/readers.js +0 -333
- package/io/writers.cjs +0 -196
- package/io/writers.d.cts +0 -22
- package/io/writers.d.ts +0 -22
- package/io/writers.js +0 -195
- package/types/agent.d.ts +0 -72
- package/types/dialer.d.ts +0 -30
- package/types/index.d.cts +0 -2
- package/types/index.d.ts +0 -2
package/README.md
CHANGED
|
@@ -1,111 +1,204 @@
|
|
|
1
1
|
# @npy/fetch
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An HTTP/1.1 client built on raw TCP sockets with a fetch-compatible API, per-origin connection pooling, and first-class proxy support.
|
|
4
4
|
|
|
5
5
|
> [!NOTE]
|
|
6
|
-
>
|
|
6
|
+
> Node.js and Bun only — does not work in the browser.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
7
9
|
|
|
8
|
-
## install
|
|
9
10
|
```sh
|
|
10
11
|
bun add @npy/fetch
|
|
11
|
-
# or
|
|
12
12
|
npm install @npy/fetch
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
### Simple fetch
|
|
16
18
|
|
|
17
|
-
### simple fetch
|
|
18
19
|
```ts
|
|
19
20
|
import { fetch } from '@npy/fetch'
|
|
20
21
|
|
|
21
|
-
const response = await fetch('https://httpbin.org/
|
|
22
|
-
const
|
|
23
|
-
console.log(
|
|
22
|
+
const response = await fetch('https://httpbin.org/get')
|
|
23
|
+
const data = await response.json()
|
|
24
|
+
console.log(data)
|
|
24
25
|
|
|
25
|
-
fetch.close()
|
|
26
|
+
await fetch.close()
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
###
|
|
29
|
-
```ts
|
|
30
|
-
import { createFetch, HttpClient } from '@npy/fetch'
|
|
31
|
-
import { ProxyDialer } from '@npy/fetch/dialers'
|
|
29
|
+
### With proxy
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
dialer: new ProxyDialer('http://user:pass@proxy.example.com:8080'),
|
|
35
|
-
}))
|
|
31
|
+
Pass a proxy URL directly to the fetch options:
|
|
36
32
|
|
|
37
|
-
|
|
33
|
+
```ts
|
|
34
|
+
import { fetch } from '@npy/fetch'
|
|
35
|
+
|
|
36
|
+
const response = await fetch('https://httpbin.org/ip', {
|
|
37
|
+
proxy: 'http://user:password@proxy.example.com:8080'
|
|
38
|
+
})
|
|
38
39
|
console.log(await response.json())
|
|
39
40
|
|
|
40
|
-
fetch.close()
|
|
41
|
+
await fetch.close()
|
|
41
42
|
```
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
Supports HTTP, HTTPS, and SOCKS5 proxies:
|
|
45
|
+
|
|
44
46
|
```ts
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
// SOCKS5
|
|
48
|
+
await fetch('https://example.com', {
|
|
49
|
+
proxy: 'socks5://user:password@proxy.example.com:1080'
|
|
50
|
+
})
|
|
51
|
+
```
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
dialer: new ProxyDialer('socks5://user:pass@proxy.example.com:1080'),
|
|
50
|
-
}))
|
|
53
|
+
### POST request
|
|
51
54
|
|
|
52
|
-
|
|
55
|
+
```ts
|
|
56
|
+
import { fetch } from '@npy/fetch'
|
|
57
|
+
|
|
58
|
+
const response = await fetch('https://httpbin.org/post', {
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: JSON.stringify({ hello: 'world' })
|
|
62
|
+
})
|
|
53
63
|
console.log(await response.json())
|
|
54
64
|
|
|
55
|
-
fetch.close()
|
|
65
|
+
await fetch.close()
|
|
56
66
|
```
|
|
57
67
|
|
|
58
|
-
###
|
|
68
|
+
### Custom HTTP client
|
|
69
|
+
|
|
70
|
+
For fine-grained control over connection pooling and I/O:
|
|
71
|
+
|
|
59
72
|
```ts
|
|
60
73
|
import { HttpClient } from '@npy/fetch'
|
|
61
74
|
|
|
62
75
|
const client = new HttpClient({
|
|
63
|
-
poolMaxPerHost:
|
|
64
|
-
poolMaxIdlePerHost:
|
|
65
|
-
poolIdleTimeout: 30_000,
|
|
76
|
+
poolMaxPerHost: 32,
|
|
77
|
+
poolMaxIdlePerHost: 8,
|
|
66
78
|
connect: {
|
|
67
79
|
keepAlive: true,
|
|
68
80
|
noDelay: true,
|
|
69
|
-
timeout: 5_000,
|
|
70
81
|
},
|
|
71
82
|
io: {
|
|
72
83
|
reader: {
|
|
73
84
|
bufferSize: 32 * 1024,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
maxBodySize: '10mb',
|
|
85
|
+
maxHeaderSize: 64 * 1024,
|
|
86
|
+
maxBodySize: '50mb',
|
|
77
87
|
decompress: true,
|
|
78
88
|
},
|
|
79
89
|
writer: {
|
|
80
|
-
|
|
81
|
-
|
|
90
|
+
writeBufferSize: 16 * 1024,
|
|
91
|
+
directWriteThreshold: 64 * 1024,
|
|
82
92
|
},
|
|
83
93
|
},
|
|
84
94
|
})
|
|
85
95
|
|
|
86
|
-
const response = await client.send({
|
|
87
|
-
|
|
96
|
+
const response = await client.send({
|
|
97
|
+
url: 'https://httpbin.org/post',
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: new Headers({ 'content-type': 'application/json' }),
|
|
100
|
+
body: JSON.stringify({ data: 'example' }),
|
|
101
|
+
})
|
|
88
102
|
|
|
103
|
+
console.log(await response.json())
|
|
89
104
|
await client.close()
|
|
90
105
|
```
|
|
91
106
|
|
|
92
|
-
###
|
|
93
|
-
```ts
|
|
94
|
-
import { HttpClient } from '@npy/fetch'
|
|
107
|
+
### With ProxyDialer
|
|
95
108
|
|
|
96
|
-
|
|
109
|
+
Use `ProxyDialer` for explicit proxy configuration with custom HTTP client:
|
|
97
110
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
111
|
+
```ts
|
|
112
|
+
import { createFetch, HttpClient } from '@npy/fetch'
|
|
113
|
+
import { ProxyDialer } from '@npy/fetch/dialers'
|
|
114
|
+
|
|
115
|
+
const client = new HttpClient({
|
|
116
|
+
dialer: new ProxyDialer('http://user:password@proxy.example.com:8080'),
|
|
117
|
+
poolMaxPerHost: 16,
|
|
118
|
+
poolMaxIdlePerHost: 4,
|
|
103
119
|
})
|
|
104
120
|
|
|
121
|
+
const fetch = createFetch(client)
|
|
122
|
+
const response = await fetch('https://httpbin.org/ip')
|
|
105
123
|
console.log(await response.json())
|
|
124
|
+
|
|
106
125
|
await client.close()
|
|
107
126
|
```
|
|
108
127
|
|
|
109
|
-
|
|
128
|
+
Supports all proxy protocols:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
// HTTP proxy
|
|
132
|
+
new ProxyDialer('http://proxy.example.com:8080')
|
|
133
|
+
|
|
134
|
+
// HTTPS proxy
|
|
135
|
+
new ProxyDialer('https://proxy.example.com:8443')
|
|
136
|
+
|
|
137
|
+
// SOCKS5 proxy
|
|
138
|
+
new ProxyDialer('socks5://proxy.example.com:1080')
|
|
139
|
+
|
|
140
|
+
// With authentication
|
|
141
|
+
new ProxyDialer('socks5://user:password@proxy.example.com:1080')
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
### `fetch(input, init?)`
|
|
147
|
+
|
|
148
|
+
Standard Fetch API with additional options:
|
|
149
|
+
|
|
150
|
+
- `proxy?: string | ProxyInfo | null` — Proxy URL (http, https, socks5)
|
|
151
|
+
- `client?: HttpClient` — Custom HTTP client instance
|
|
152
|
+
|
|
153
|
+
### `HttpClient` options
|
|
154
|
+
|
|
155
|
+
**Pool:**
|
|
156
|
+
|
|
157
|
+
- `poolMaxPerHost` (default: `10`)
|
|
158
|
+
- `poolMaxIdlePerHost` (default: `5`)
|
|
159
|
+
- `poolIdleTimeout` (default: `30000` ms)
|
|
160
|
+
|
|
161
|
+
**Connection:**
|
|
162
|
+
|
|
163
|
+
- `connect.timeout` (default: `5000` ms)
|
|
164
|
+
- `connect.keepAlive` (default: `true`)
|
|
165
|
+
- `connect.noDelay` (default: `true`)
|
|
166
|
+
|
|
167
|
+
**Reader:**
|
|
168
|
+
|
|
169
|
+
- `io.reader.bufferSize` — Internal buffer size
|
|
170
|
+
- `io.reader.readChunkSize` — Chunk read size
|
|
171
|
+
- `io.reader.maxHeaderSize` — Maximum header size
|
|
172
|
+
- `io.reader.maxBodySize` — Maximum body size
|
|
173
|
+
- `io.reader.maxLineSize` — Maximum line size
|
|
174
|
+
- `io.reader.maxBufferedBytes` — Max buffered before processing
|
|
175
|
+
- `io.reader.decompress` (default: `true`) — Auto-decompress gzip/deflate
|
|
176
|
+
|
|
177
|
+
**Writer:**
|
|
178
|
+
|
|
179
|
+
- `io.writer.writeBufferSize` — Write buffer size
|
|
180
|
+
- `io.writer.directWriteThreshold` — Direct write threshold
|
|
181
|
+
- `io.writer.coalesceBodyMaxBytes` — Coalesce writes up to this size
|
|
182
|
+
|
|
183
|
+
## Environment Variables
|
|
184
|
+
|
|
185
|
+
The library respects standard proxy environment variables:
|
|
186
|
+
|
|
187
|
+
- `HTTP_PROXY` — HTTP proxy URL
|
|
188
|
+
- `HTTPS_PROXY` — HTTPS proxy URL
|
|
189
|
+
- `SOCKS5_PROXY` — SOCKS5 proxy URL
|
|
190
|
+
- `SOCKS_PROXY` — Alternative SOCKS proxy URL
|
|
191
|
+
|
|
192
|
+
These are used when no explicit proxy is configured.
|
|
193
|
+
|
|
194
|
+
## Limitations
|
|
195
|
+
|
|
196
|
+
- **HTTP/1.1 only** — Does not support HTTP/2 or HTTP/3
|
|
197
|
+
- **Node.js and Bun** — Browser environments are not supported
|
|
198
|
+
- **Limited to standard HTTP methods** — GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
|
|
199
|
+
|
|
200
|
+
## License
|
|
201
|
+
|
|
202
|
+
MIT License © 2026 matheus fernandes
|
|
110
203
|
|
|
111
|
-
|
|
204
|
+
Based on [deno-simple-fetch](https://github.com/esroyo/deno-simple-fetch).
|
package/bun.lock
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "@npy/fetch",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@fuman/io": "^0.0.19",
|
|
9
|
+
"@fuman/net": "^0.0.19",
|
|
10
|
+
"@fuman/node": "^0.0.19",
|
|
11
|
+
"@fuman/utils": "^0.0.19",
|
|
12
|
+
"bytes": "^3.1.2",
|
|
13
|
+
"generic-pool": "^3.9.0",
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@biomejs/biome": "2.4.9",
|
|
17
|
+
"@types/bun": "latest",
|
|
18
|
+
"@types/bytes": "^3.1.5",
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"typescript": "^5",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"packages": {
|
|
26
|
+
"@biomejs/biome": ["@biomejs/biome@2.4.9", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.9", "@biomejs/cli-darwin-x64": "2.4.9", "@biomejs/cli-linux-arm64": "2.4.9", "@biomejs/cli-linux-arm64-musl": "2.4.9", "@biomejs/cli-linux-x64": "2.4.9", "@biomejs/cli-linux-x64-musl": "2.4.9", "@biomejs/cli-win32-arm64": "2.4.9", "@biomejs/cli-win32-x64": "2.4.9" }, "bin": { "biome": "bin/biome" } }, "sha512-wvZW92FrwitTcacvCBT8xdAbfbxWfDLwjYMmU3djjqQTh7Ni4ZdiWIT/x5VcZ+RQuxiKzIOzi5D+dcyJDFZMsA=="],
|
|
27
|
+
|
|
28
|
+
"@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-d5G8Gf2RpH5pYwiHLPA+UpG3G9TLQu4WM+VK6sfL7K68AmhcEQ9r+nkj/DvR/GYhYox6twsHUtmWWWIKfcfQQA=="],
|
|
29
|
+
|
|
30
|
+
"@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.4.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-LNCLNgqDMG7BLdc3a8aY/dwKPK7+R8/JXJoXjCvZh2gx8KseqBdFDKbhrr7HCWF8SzNhbTaALhTBoh/I6rf9lA=="],
|
|
31
|
+
|
|
32
|
+
"@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.4.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-4adnkAUi6K4C/emPRgYznMOcLlUqZdXWM6aIui4VP4LraE764g6Q4YguygnAUoxKjKIXIWPteKMgRbN0wsgwcg=="],
|
|
33
|
+
|
|
34
|
+
"@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.4.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-8RCww5xnPn2wpK4L/QDGDOW0dq80uVWfppPxHIUg6mOs9B6gRmqPp32h1Ls3T8GnW8Wo5A8u7vpTwz4fExN+sw=="],
|
|
35
|
+
|
|
36
|
+
"@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.4.9", "", { "os": "linux", "cpu": "x64" }, "sha512-L10na7POF0Ks/cgLFNF1ZvIe+X4onLkTi5oP9hY+Rh60Q+7fWzKDDCeGyiHUFf1nGIa9dQOOUPGe2MyYg8nMSQ=="],
|
|
37
|
+
|
|
38
|
+
"@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.4.9", "", { "os": "linux", "cpu": "x64" }, "sha512-5TD+WS9v5vzXKzjetF0hgoaNFHMcpQeBUwKKVi3JbG1e9UCrFuUK3Gt185fyTzvRdwYkJJEMqglRPjmesmVv4A=="],
|
|
39
|
+
|
|
40
|
+
"@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.4.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-aDZr0RBC3sMGJOU10BvG7eZIlWLK/i51HRIfScE2lVhfts2dQTreowLiJJd+UYg/tHKxS470IbzpuKmd0MiD6g=="],
|
|
41
|
+
|
|
42
|
+
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.4.9", "", { "os": "win32", "cpu": "x64" }, "sha512-NS4g/2G9SoQ4ktKtz31pvyc/rmgzlcIDCGU/zWbmHJAqx6gcRj2gj5Q/guXhoWTzCUaQZDIqiCQXHS7BcGYc0w=="],
|
|
43
|
+
|
|
44
|
+
"@fuman/io": ["@fuman/io@0.0.19", "", { "dependencies": { "@fuman/utils": "^0.0.19" } }, "sha512-B+2n3GVa9PCYMJ9xfsdXUlUV9yXO4gKLYfxm815PeJ+MGOw5TbEp166drRmBq1AtxVnP0efy6Oz9rYpKVODgow=="],
|
|
45
|
+
|
|
46
|
+
"@fuman/net": ["@fuman/net@0.0.19", "", { "dependencies": { "@fuman/io": "^0.0.19", "@fuman/utils": "^0.0.19" } }, "sha512-yISM+JcZEWBpBYn0v2mUY/Zst4SsicTRaVTvRkVhMiZhgMzdXalfvRwRV/vsgwwL31bntwowCTDW4iilCJLbXg=="],
|
|
47
|
+
|
|
48
|
+
"@fuman/node": ["@fuman/node@0.0.19", "", { "dependencies": { "@fuman/io": "^0.0.19", "@fuman/net": "^0.0.19", "@fuman/utils": "^0.0.19" }, "peerDependencies": { "ws": "^8.18.1" }, "optionalPeers": ["ws"] }, "sha512-1VNTBb47yrN5BzuXiP4t6An7mDPklH5N+vUtkeL3XATK+xWbtlQsSsU244T7iqGurmDpYrLM9kIUjdMFm8OhDw=="],
|
|
49
|
+
|
|
50
|
+
"@fuman/utils": ["@fuman/utils@0.0.19", "", {}, "sha512-4qVrZ9AjKYztLJsNr1Tp7kL48b22dvVLN1iVW+Me8ZSQ0ILN0qknoxjsczVPReF7+GDWgknNxR2l6ggrA4SZyw=="],
|
|
51
|
+
|
|
52
|
+
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
|
|
53
|
+
|
|
54
|
+
"@types/bytes": ["@types/bytes@3.1.5", "", {}, "sha512-VgZkrJckypj85YxEsEavcMmmSOIzkUHqWmM4CCyia5dc54YwsXzJ5uT4fYxBQNEXx+oF1krlhgCbvfubXqZYsQ=="],
|
|
55
|
+
|
|
56
|
+
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
|
|
57
|
+
|
|
58
|
+
"bun-types": ["bun-types@1.3.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg=="],
|
|
59
|
+
|
|
60
|
+
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
|
|
61
|
+
|
|
62
|
+
"generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="],
|
|
63
|
+
|
|
64
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
65
|
+
|
|
66
|
+
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ProxyDialer } from "../src/dialers/proxy";
|
|
2
|
+
import { createFetch } from "../src/fetch";
|
|
3
|
+
import { HttpClient } from "../src/http-client";
|
|
4
|
+
|
|
5
|
+
const proxyUrl = process.env.HTTP_PROXY as string;
|
|
6
|
+
|
|
7
|
+
const client = new HttpClient({
|
|
8
|
+
dialer: new ProxyDialer(proxyUrl),
|
|
9
|
+
poolMaxPerHost: 16,
|
|
10
|
+
poolMaxIdlePerHost: 4,
|
|
11
|
+
connect: {
|
|
12
|
+
keepAlive: true,
|
|
13
|
+
noDelay: true,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const fetch = createFetch(client);
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch("https://httpbin.org/ip");
|
|
21
|
+
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`HTTP ${response.status}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
|
|
28
|
+
console.log("proxy:", proxyUrl);
|
|
29
|
+
console.log("origin seen by server:", data.origin);
|
|
30
|
+
} finally {
|
|
31
|
+
await client.close();
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { HttpClient } from "../src/http-client";
|
|
2
|
+
|
|
3
|
+
const client = new HttpClient({
|
|
4
|
+
poolMaxPerHost: 32,
|
|
5
|
+
poolMaxIdlePerHost: 8,
|
|
6
|
+
connect: {
|
|
7
|
+
keepAlive: true,
|
|
8
|
+
noDelay: true,
|
|
9
|
+
},
|
|
10
|
+
io: {
|
|
11
|
+
reader: {
|
|
12
|
+
highWaterMark: 16 * 1024,
|
|
13
|
+
maxHeaderSize: 64 * 1024,
|
|
14
|
+
maxLineSize: 64 * 1024,
|
|
15
|
+
maxBufferedBytes: 256 * 1024,
|
|
16
|
+
decompress: true,
|
|
17
|
+
},
|
|
18
|
+
writer: {
|
|
19
|
+
highWaterMark: 16 * 1024,
|
|
20
|
+
directWriteThreshold: 64 * 1024,
|
|
21
|
+
coalesceBodyMaxBytes: 64 * 1024,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const response = await client.send({
|
|
28
|
+
url: "https://httpbin.org/anything",
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: new Headers({
|
|
31
|
+
"content-type": "application/json",
|
|
32
|
+
"x-example": "advanced-http-client",
|
|
33
|
+
}),
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
source: "HttpClient.send",
|
|
36
|
+
features: ["pooling", "custom I/O", "raw Response access"],
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
console.log("status:", response.status);
|
|
41
|
+
console.log("headers:", Object.fromEntries(response.headers.entries()));
|
|
42
|
+
|
|
43
|
+
const data = await response.json();
|
|
44
|
+
console.log("echoed json:", data.json);
|
|
45
|
+
} finally {
|
|
46
|
+
await client.close();
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { fetch } from "../src/fetch";
|
|
2
|
+
|
|
3
|
+
const proxyUrl = "http://V0rk3M:phA3fT@186.179.61.64:9183";
|
|
4
|
+
|
|
5
|
+
const response = await fetch("https://httpbin.org/ip", { proxy: proxyUrl });
|
|
6
|
+
|
|
7
|
+
if (!response.ok) {
|
|
8
|
+
throw new Error(`HTTP ${response.status}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const data = await response.json();
|
|
12
|
+
|
|
13
|
+
console.log("proxy:", proxyUrl);
|
|
14
|
+
console.log("origin seen by server:", data.origin);
|
|
15
|
+
|
|
16
|
+
await fetch.close();
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { fetch } from "../src/fetch";
|
|
2
|
+
|
|
3
|
+
const response = await fetch("https://httpbin.org/anything");
|
|
4
|
+
|
|
5
|
+
if (!response.ok) {
|
|
6
|
+
throw new Error(`HTTP ${response.status}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const data = await response.json();
|
|
10
|
+
|
|
11
|
+
console.log("status:", response.status);
|
|
12
|
+
console.log("url:", data.url);
|
|
13
|
+
console.log("headers sent:", data.headers);
|
|
14
|
+
|
|
15
|
+
await fetch.close();
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@npy/fetch",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
5
|
-
"description": "HTTP/1.1 client built from raw TCP sockets with fetch-compatible primitives and proxy support.",
|
|
4
|
+
"version": "0.1.3",
|
|
6
5
|
"license": "MIT",
|
|
6
|
+
"description": "HTTP/1.1 client built from raw TCP sockets with fetch-compatible primitives and proxy support.",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
7
11
|
"keywords": [
|
|
8
12
|
"fetch",
|
|
9
13
|
"http",
|
|
@@ -15,38 +19,29 @@
|
|
|
15
19
|
"node",
|
|
16
20
|
"bun"
|
|
17
21
|
],
|
|
18
|
-
"scripts": {
|
|
22
|
+
"scripts": {
|
|
23
|
+
"bench": "node --import=tsx ./benchmarks/index.ts",
|
|
24
|
+
"bench:direct": "node --import=tsx ./benchmarks/direct.ts",
|
|
25
|
+
"bench:proxy": "node --import=tsx ./benchmarks/proxy.ts"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/bytes": "^3.1.5",
|
|
29
|
+
"axios": "^1.8.4",
|
|
30
|
+
"got": "^14.4.6",
|
|
31
|
+
"hpagent": "^1.2.0",
|
|
32
|
+
"http-proxy-agent": "^7.0.2",
|
|
33
|
+
"node-fetch": "^3.3.2",
|
|
34
|
+
"proxy-chain": "^2.7.1",
|
|
35
|
+
"undici": "^7.13.0",
|
|
36
|
+
"mitata": "^1.0.34"
|
|
37
|
+
},
|
|
19
38
|
"dependencies": {
|
|
20
39
|
"@fuman/io": "^0.0.19",
|
|
21
40
|
"@fuman/net": "^0.0.19",
|
|
22
41
|
"@fuman/node": "^0.0.19",
|
|
23
42
|
"@fuman/utils": "^0.0.19",
|
|
24
|
-
"@npy/proxy-kit": "
|
|
43
|
+
"@npy/proxy-kit": "workspace:*",
|
|
25
44
|
"bytes": "^3.1.2",
|
|
26
45
|
"generic-pool": "^3.9.0"
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
".": {
|
|
30
|
-
"import": {
|
|
31
|
-
"types": "./index.d.ts",
|
|
32
|
-
"default": "./index.js"
|
|
33
|
-
},
|
|
34
|
-
"require": {
|
|
35
|
-
"types": "./index.d.cts",
|
|
36
|
-
"default": "./index.cjs"
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"sideEffects": false,
|
|
41
|
-
"homepage": "https://github.com/neeopy/npy",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/neeopy/npy.git"
|
|
45
|
-
},
|
|
46
|
-
"bugs": {
|
|
47
|
-
"url": "https://github.com/neeopy/npy/issues"
|
|
48
|
-
},
|
|
49
|
-
"files": [
|
|
50
|
-
"**/*"
|
|
51
|
-
]
|
|
52
|
-
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -5,7 +5,12 @@
|
|
|
5
5
|
* can use `instanceof` instead of heuristics on error messages or codes.
|
|
6
6
|
* Never exposed in the public API — callers receive ResponseDecodeError.
|
|
7
7
|
*/
|
|
8
|
-
export
|
|
8
|
+
export class DecodeStreamError extends Error {
|
|
9
9
|
readonly cause: unknown;
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
constructor(cause: unknown) {
|
|
12
|
+
super("Response decode failed", { cause });
|
|
13
|
+
this.name = "DecodeStreamError";
|
|
14
|
+
this.cause = cause;
|
|
15
|
+
}
|
|
11
16
|
}
|