@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.
Files changed (142) 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 +25 -30
  8. package/src/_internal/consts.ts +3 -0
  9. package/{_internal/decode-stream-error.d.cts → src/_internal/decode-stream-error.ts} +7 -2
  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/{types/agent.d.cts → src/types/agent.ts} +47 -21
  33. package/{types/dialer.d.cts → src/types/dialer.ts} +19 -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.d.cts +0 -3
  47. package/_internal/consts.d.ts +0 -3
  48. package/_internal/consts.js +0 -4
  49. package/_internal/decode-stream-error.cjs +0 -18
  50. package/_internal/decode-stream-error.d.ts +0 -11
  51. package/_internal/decode-stream-error.js +0 -18
  52. package/_internal/error-mapping.cjs +0 -44
  53. package/_internal/error-mapping.d.cts +0 -15
  54. package/_internal/error-mapping.d.ts +0 -15
  55. package/_internal/error-mapping.js +0 -41
  56. package/_internal/guards.cjs +0 -23
  57. package/_internal/guards.d.cts +0 -15
  58. package/_internal/guards.d.ts +0 -15
  59. package/_internal/guards.js +0 -15
  60. package/_internal/net.cjs +0 -95
  61. package/_internal/net.d.cts +0 -11
  62. package/_internal/net.d.ts +0 -11
  63. package/_internal/net.js +0 -92
  64. package/_internal/promises.cjs +0 -18
  65. package/_internal/promises.d.cts +0 -1
  66. package/_internal/promises.d.ts +0 -1
  67. package/_internal/promises.js +0 -18
  68. package/_internal/streams.cjs +0 -37
  69. package/_internal/streams.d.cts +0 -21
  70. package/_internal/streams.d.ts +0 -21
  71. package/_internal/streams.js +0 -36
  72. package/_internal/symbols.cjs +0 -4
  73. package/_internal/symbols.d.cts +0 -1
  74. package/_internal/symbols.d.ts +0 -1
  75. package/_internal/symbols.js +0 -4
  76. package/_virtual/_rolldown/runtime.cjs +0 -23
  77. package/agent-pool.cjs +0 -96
  78. package/agent-pool.d.cts +0 -2
  79. package/agent-pool.d.ts +0 -2
  80. package/agent-pool.js +0 -95
  81. package/agent.cjs +0 -260
  82. package/agent.d.cts +0 -3
  83. package/agent.d.ts +0 -3
  84. package/agent.js +0 -259
  85. package/body.cjs +0 -105
  86. package/body.d.cts +0 -12
  87. package/body.d.ts +0 -12
  88. package/body.js +0 -102
  89. package/dialers/index.d.cts +0 -3
  90. package/dialers/index.d.ts +0 -3
  91. package/dialers/proxy.cjs +0 -56
  92. package/dialers/proxy.d.cts +0 -27
  93. package/dialers/proxy.d.ts +0 -27
  94. package/dialers/proxy.js +0 -55
  95. package/dialers/tcp.cjs +0 -92
  96. package/dialers/tcp.d.cts +0 -57
  97. package/dialers/tcp.d.ts +0 -57
  98. package/dialers/tcp.js +0 -89
  99. package/encoding.cjs +0 -114
  100. package/encoding.d.cts +0 -35
  101. package/encoding.d.ts +0 -35
  102. package/encoding.js +0 -110
  103. package/errors.cjs +0 -275
  104. package/errors.d.cts +0 -110
  105. package/errors.d.ts +0 -110
  106. package/errors.js +0 -259
  107. package/fetch.cjs +0 -353
  108. package/fetch.d.cts +0 -58
  109. package/fetch.d.ts +0 -58
  110. package/fetch.js +0 -350
  111. package/http-client.cjs +0 -75
  112. package/http-client.d.cts +0 -39
  113. package/http-client.d.ts +0 -39
  114. package/http-client.js +0 -75
  115. package/index.cjs +0 -49
  116. package/index.d.cts +0 -14
  117. package/index.d.ts +0 -14
  118. package/index.js +0 -11
  119. package/io/_utils.cjs +0 -56
  120. package/io/_utils.d.cts +0 -10
  121. package/io/_utils.d.ts +0 -10
  122. package/io/_utils.js +0 -51
  123. package/io/buf-writer.cjs +0 -149
  124. package/io/buf-writer.d.cts +0 -13
  125. package/io/buf-writer.d.ts +0 -13
  126. package/io/buf-writer.js +0 -148
  127. package/io/io.cjs +0 -199
  128. package/io/io.d.cts +0 -5
  129. package/io/io.d.ts +0 -5
  130. package/io/io.js +0 -198
  131. package/io/readers.cjs +0 -337
  132. package/io/readers.d.cts +0 -69
  133. package/io/readers.d.ts +0 -69
  134. package/io/readers.js +0 -333
  135. package/io/writers.cjs +0 -196
  136. package/io/writers.d.cts +0 -22
  137. package/io/writers.d.ts +0 -22
  138. package/io/writers.js +0 -195
  139. package/types/agent.d.ts +0 -72
  140. package/types/dialer.d.ts +0 -30
  141. package/types/index.d.cts +0 -2
  142. package/types/index.d.ts +0 -2
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,9 +1,13 @@
1
1
  {
2
2
  "name": "@npy/fetch",
3
3
  "type": "module",
4
- "version": "0.1.2",
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": "0.1.2",
43
+ "@npy/proxy-kit": "workspace:*",
25
44
  "bytes": "^3.1.2",
26
45
  "generic-pool": "^3.9.0"
27
- },
28
- "exports": {
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
+ }
@@ -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;
@@ -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 declare class DecodeStreamError extends Error {
8
+ export class DecodeStreamError extends Error {
9
9
  readonly cause: unknown;
10
- constructor(cause: unknown);
10
+
11
+ constructor(cause: unknown) {
12
+ super("Response decode failed", { cause });
13
+ this.name = "DecodeStreamError";
14
+ this.cause = cause;
15
+ }
11
16
  }