@npy/fetch 0.1.1 → 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.
Files changed (140) hide show
  1. package/README.md +143 -50
  2. package/bun.lock +68 -0
  3. package/examples/custom-proxy-client.ts +32 -0
  4. package/examples/http-client.ts +47 -0
  5. package/examples/proxy.ts +16 -0
  6. package/examples/simple.ts +15 -0
  7. package/package.json +36 -25
  8. package/src/_internal/consts.ts +3 -0
  9. package/src/_internal/decode-stream-error.ts +16 -0
  10. package/src/_internal/error-mapping.ts +160 -0
  11. package/src/_internal/guards.ts +78 -0
  12. package/src/_internal/net.ts +173 -0
  13. package/src/_internal/promises.ts +22 -0
  14. package/src/_internal/streams.ts +52 -0
  15. package/src/_internal/symbols.ts +1 -0
  16. package/src/agent-pool.ts +157 -0
  17. package/src/agent.ts +408 -0
  18. package/src/body.ts +179 -0
  19. package/src/dialers/index.ts +3 -0
  20. package/src/dialers/proxy.ts +102 -0
  21. package/src/dialers/tcp.ts +162 -0
  22. package/src/encoding.ts +222 -0
  23. package/src/errors.ts +357 -0
  24. package/src/fetch.ts +626 -0
  25. package/src/http-client.ts +111 -0
  26. package/src/index.ts +14 -0
  27. package/src/io/_utils.ts +82 -0
  28. package/src/io/buf-writer.ts +183 -0
  29. package/src/io/io.ts +322 -0
  30. package/src/io/readers.ts +576 -0
  31. package/src/io/writers.ts +331 -0
  32. package/src/types/agent.ts +98 -0
  33. package/src/types/{dialer.d.cts → dialer.ts} +22 -9
  34. package/src/types/index.ts +2 -0
  35. package/tests/agent-pool.test.ts +111 -0
  36. package/tests/agent.test.ts +134 -0
  37. package/tests/body.test.ts +228 -0
  38. package/tests/errors.test.ts +152 -0
  39. package/tests/fetch.test.ts +421 -0
  40. package/tests/io-options.test.ts +127 -0
  41. package/tests/multipart.test.ts +348 -0
  42. package/tests/test-utils.ts +335 -0
  43. package/tsconfig.json +15 -0
  44. package/LICENSE +0 -21
  45. package/_internal/consts.cjs +0 -4
  46. package/_internal/consts.js +0 -4
  47. package/_internal/error-adapters.cjs +0 -146
  48. package/_internal/error-adapters.js +0 -142
  49. package/_internal/guards.cjs +0 -24
  50. package/_internal/guards.js +0 -17
  51. package/_internal/net.cjs +0 -95
  52. package/_internal/net.js +0 -92
  53. package/_internal/promises.cjs +0 -18
  54. package/_internal/promises.js +0 -18
  55. package/_internal/streams.cjs +0 -37
  56. package/_internal/streams.js +0 -36
  57. package/_virtual/_rolldown/runtime.cjs +0 -23
  58. package/agent-pool.cjs +0 -78
  59. package/agent-pool.js +0 -77
  60. package/agent.cjs +0 -257
  61. package/agent.js +0 -256
  62. package/body.cjs +0 -154
  63. package/body.js +0 -151
  64. package/dialers/proxy.cjs +0 -49
  65. package/dialers/proxy.js +0 -48
  66. package/dialers/tcp.cjs +0 -70
  67. package/dialers/tcp.js +0 -67
  68. package/encoding.cjs +0 -95
  69. package/encoding.js +0 -91
  70. package/errors.cjs +0 -275
  71. package/errors.js +0 -259
  72. package/fetch.cjs +0 -117
  73. package/fetch.js +0 -115
  74. package/http-client.cjs +0 -33
  75. package/http-client.js +0 -33
  76. package/index.cjs +0 -45
  77. package/index.d.cts +0 -1
  78. package/index.d.ts +0 -1
  79. package/index.js +0 -9
  80. package/io/_utils.cjs +0 -56
  81. package/io/_utils.js +0 -51
  82. package/io/buf-writer.cjs +0 -149
  83. package/io/buf-writer.js +0 -148
  84. package/io/io.cjs +0 -135
  85. package/io/io.js +0 -134
  86. package/io/readers.cjs +0 -377
  87. package/io/readers.js +0 -373
  88. package/io/writers.cjs +0 -191
  89. package/io/writers.js +0 -190
  90. package/src/_internal/consts.d.cts +0 -3
  91. package/src/_internal/consts.d.ts +0 -3
  92. package/src/_internal/error-adapters.d.cts +0 -22
  93. package/src/_internal/error-adapters.d.ts +0 -22
  94. package/src/_internal/guards.d.cts +0 -13
  95. package/src/_internal/guards.d.ts +0 -13
  96. package/src/_internal/net.d.cts +0 -12
  97. package/src/_internal/net.d.ts +0 -12
  98. package/src/_internal/promises.d.cts +0 -1
  99. package/src/_internal/promises.d.ts +0 -1
  100. package/src/_internal/streams.d.cts +0 -21
  101. package/src/_internal/streams.d.ts +0 -21
  102. package/src/agent-pool.d.cts +0 -2
  103. package/src/agent-pool.d.ts +0 -2
  104. package/src/agent.d.cts +0 -3
  105. package/src/agent.d.ts +0 -3
  106. package/src/body.d.cts +0 -23
  107. package/src/body.d.ts +0 -23
  108. package/src/dialers/index.d.cts +0 -3
  109. package/src/dialers/index.d.ts +0 -3
  110. package/src/dialers/proxy.d.cts +0 -19
  111. package/src/dialers/proxy.d.ts +0 -19
  112. package/src/dialers/tcp.d.cts +0 -36
  113. package/src/dialers/tcp.d.ts +0 -36
  114. package/src/encoding.d.cts +0 -24
  115. package/src/encoding.d.ts +0 -24
  116. package/src/errors.d.cts +0 -110
  117. package/src/errors.d.ts +0 -110
  118. package/src/fetch.d.cts +0 -36
  119. package/src/fetch.d.ts +0 -36
  120. package/src/http-client.d.cts +0 -23
  121. package/src/http-client.d.ts +0 -23
  122. package/src/index.d.cts +0 -7
  123. package/src/index.d.ts +0 -7
  124. package/src/io/_utils.d.cts +0 -10
  125. package/src/io/_utils.d.ts +0 -10
  126. package/src/io/buf-writer.d.cts +0 -13
  127. package/src/io/buf-writer.d.ts +0 -13
  128. package/src/io/io.d.cts +0 -5
  129. package/src/io/io.d.ts +0 -5
  130. package/src/io/readers.d.cts +0 -199
  131. package/src/io/readers.d.ts +0 -199
  132. package/src/io/writers.d.cts +0 -22
  133. package/src/io/writers.d.ts +0 -22
  134. package/src/types/agent.d.cts +0 -128
  135. package/src/types/agent.d.ts +0 -128
  136. package/src/types/dialer.d.ts +0 -27
  137. package/src/types/index.d.cts +0 -2
  138. package/src/types/index.d.ts +0 -2
  139. package/tests/test-utils.d.cts +0 -8
  140. package/tests/test-utils.d.ts +0 -8
package/README.md CHANGED
@@ -1,111 +1,204 @@
1
1
  # @npy/fetch
2
2
 
3
- an http/1.1 client built over raw tcp sockets with a fetch-compatible api, per-origin connection pooling, and first-class proxy support.
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
- > node and bun only — does not work in the browser.
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
- ## usage
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/anything')
22
- const body = await response.json()
23
- console.log(body)
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
- ### with http/s proxy
29
- ```ts
30
- import { createFetch, HttpClient } from '@npy/fetch'
31
- import { ProxyDialer } from '@npy/fetch/dialers'
29
+ ### With proxy
32
30
 
33
- const fetch = createFetch(new HttpClient({
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
- const response = await fetch('https://httpbin.org/ip')
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
- ### with socks proxy
44
+ Supports HTTP, HTTPS, and SOCKS5 proxies:
45
+
44
46
  ```ts
45
- import { createFetch, HttpClient } from '@npy/fetch'
46
- import { ProxyDialer } from '@npy/fetch/dialers'
47
+ // SOCKS5
48
+ await fetch('https://example.com', {
49
+ proxy: 'socks5://user:password@proxy.example.com:1080'
50
+ })
51
+ ```
47
52
 
48
- const fetch = createFetch(new HttpClient({
49
- dialer: new ProxyDialer('socks5://user:pass@proxy.example.com:1080'),
50
- }))
53
+ ### POST request
51
54
 
52
- const response = await fetch('https://httpbin.org/ip')
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
- ### custom i/o options
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: 10,
64
- poolMaxIdlePerHost: 5,
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
- highWaterMark: 16 * 1024,
75
- maxHeaderSize: 16 * 1024,
76
- maxBodySize: '10mb',
85
+ maxHeaderSize: 64 * 1024,
86
+ maxBodySize: '50mb',
77
87
  decompress: true,
78
88
  },
79
89
  writer: {
80
- highWaterMark: 16 * 1024,
81
- coalesceBodyMaxBytes: 64 * 1024,
90
+ writeBufferSize: 16 * 1024,
91
+ directWriteThreshold: 64 * 1024,
82
92
  },
83
93
  },
84
94
  })
85
95
 
86
- const response = await client.send({ url: 'https://httpbin.org/anything', method: 'GET' })
87
- console.log(await response.json())
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
- ### using `HttpClient` directly
93
- ```ts
94
- import { HttpClient } from '@npy/fetch'
107
+ ### With ProxyDialer
95
108
 
96
- const client = new HttpClient()
109
+ Use `ProxyDialer` for explicit proxy configuration with custom HTTP client:
97
110
 
98
- const response = await client.send({
99
- url: 'https://httpbin.org/post',
100
- method: 'POST',
101
- headers: new Headers({ 'content-type': 'application/json' }),
102
- body: JSON.stringify({ hello: 'world' }),
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
- based on [deno-simple-fetch](https://github.com/esroyo/deno-simple-fetch).
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,36 +1,47 @@
1
1
  {
2
2
  "name": "@npy/fetch",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "license": "MIT",
6
- "scripts": {},
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
+ },
11
+ "keywords": [
12
+ "fetch",
13
+ "http",
14
+ "http-client",
15
+ "proxy",
16
+ "socks",
17
+ "socks5",
18
+ "tcp",
19
+ "node",
20
+ "bun"
21
+ ],
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
+ },
7
38
  "dependencies": {
8
39
  "@fuman/io": "^0.0.19",
9
40
  "@fuman/net": "^0.0.19",
10
41
  "@fuman/node": "^0.0.19",
11
42
  "@fuman/utils": "^0.0.19",
12
- "@npy/proxy-kit": "0.1.1",
43
+ "@npy/proxy-kit": "workspace:*",
13
44
  "bytes": "^3.1.2",
14
45
  "generic-pool": "^3.9.0"
15
- },
16
- "exports": {
17
- ".": {
18
- "import": {
19
- "types": "./index.d.ts",
20
- "default": "./index.js"
21
- },
22
- "require": {
23
- "types": "./index.d.cts",
24
- "default": "./index.cjs"
25
- }
26
- }
27
- },
28
- "sideEffects": false,
29
- "repository": {
30
- "type": "git",
31
- "url": "git+https://github.com/neeopy/npy.git"
32
- },
33
- "files": [
34
- "**/*"
35
- ]
36
- }
46
+ }
47
+ }
@@ -0,0 +1,3 @@
1
+ export const CRLF_BYTES = new Uint8Array([0x0d, 0x0a]);
2
+ export const CRLF_STR = "\r\n";
3
+ export const CRLF_LENGTH = 2;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Internal sentinel thrown when a decompression stream fails.
3
+ *
4
+ * Tagged at the source (inside createDecoder) so that error classifiers
5
+ * can use `instanceof` instead of heuristics on error messages or codes.
6
+ * Never exposed in the public API — callers receive ResponseDecodeError.
7
+ */
8
+ export class DecodeStreamError extends Error {
9
+ readonly cause: unknown;
10
+
11
+ constructor(cause: unknown) {
12
+ super("Response decode failed", { cause });
13
+ this.name = "DecodeStreamError";
14
+ this.cause = cause;
15
+ }
16
+ }