@npy/fetch 0.1.0 → 0.1.1

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 (98) hide show
  1. package/_internal/consts.cjs +4 -0
  2. package/_internal/consts.js +4 -0
  3. package/_internal/error-adapters.cjs +146 -0
  4. package/_internal/error-adapters.js +142 -0
  5. package/_internal/guards.cjs +24 -0
  6. package/_internal/guards.js +17 -0
  7. package/_internal/net.cjs +95 -0
  8. package/_internal/net.js +92 -0
  9. package/_internal/promises.cjs +18 -0
  10. package/_internal/promises.js +18 -0
  11. package/_internal/streams.cjs +37 -0
  12. package/_internal/streams.js +36 -0
  13. package/_virtual/_rolldown/runtime.cjs +23 -0
  14. package/agent-pool.cjs +78 -0
  15. package/agent-pool.js +77 -0
  16. package/agent.cjs +257 -0
  17. package/agent.js +256 -0
  18. package/body.cjs +154 -0
  19. package/body.js +151 -0
  20. package/dialers/proxy.cjs +49 -0
  21. package/dialers/proxy.js +48 -0
  22. package/dialers/tcp.cjs +70 -0
  23. package/dialers/tcp.js +67 -0
  24. package/encoding.cjs +95 -0
  25. package/encoding.js +91 -0
  26. package/errors.cjs +275 -0
  27. package/errors.js +259 -0
  28. package/fetch.cjs +117 -0
  29. package/fetch.js +115 -0
  30. package/http-client.cjs +33 -0
  31. package/http-client.js +33 -0
  32. package/index.cjs +45 -0
  33. package/index.d.cts +1 -0
  34. package/index.d.ts +1 -0
  35. package/index.js +9 -0
  36. package/io/_utils.cjs +56 -0
  37. package/io/_utils.js +51 -0
  38. package/io/buf-writer.cjs +149 -0
  39. package/io/buf-writer.js +148 -0
  40. package/io/io.cjs +135 -0
  41. package/io/io.js +134 -0
  42. package/io/readers.cjs +377 -0
  43. package/io/readers.js +373 -0
  44. package/io/writers.cjs +191 -0
  45. package/io/writers.js +190 -0
  46. package/package.json +7 -10
  47. package/src/_internal/consts.d.cts +3 -0
  48. package/src/_internal/consts.d.ts +3 -0
  49. package/src/_internal/error-adapters.d.cts +22 -0
  50. package/src/_internal/error-adapters.d.ts +22 -0
  51. package/src/_internal/guards.d.cts +13 -0
  52. package/src/_internal/guards.d.ts +13 -0
  53. package/src/_internal/net.d.cts +12 -0
  54. package/src/_internal/net.d.ts +12 -0
  55. package/src/_internal/promises.d.cts +1 -0
  56. package/src/_internal/promises.d.ts +1 -0
  57. package/src/_internal/streams.d.cts +21 -0
  58. package/src/_internal/streams.d.ts +21 -0
  59. package/src/agent-pool.d.cts +2 -0
  60. package/src/agent-pool.d.ts +2 -0
  61. package/src/agent.d.cts +3 -0
  62. package/src/agent.d.ts +3 -0
  63. package/src/body.d.cts +23 -0
  64. package/src/body.d.ts +23 -0
  65. package/src/dialers/index.d.cts +3 -0
  66. package/src/dialers/index.d.ts +3 -0
  67. package/src/dialers/proxy.d.cts +19 -0
  68. package/src/dialers/proxy.d.ts +19 -0
  69. package/src/dialers/tcp.d.cts +36 -0
  70. package/src/dialers/tcp.d.ts +36 -0
  71. package/src/encoding.d.cts +24 -0
  72. package/src/encoding.d.ts +24 -0
  73. package/src/errors.d.cts +110 -0
  74. package/src/errors.d.ts +110 -0
  75. package/src/fetch.d.cts +36 -0
  76. package/src/fetch.d.ts +36 -0
  77. package/src/http-client.d.cts +23 -0
  78. package/src/http-client.d.ts +23 -0
  79. package/src/index.d.cts +7 -0
  80. package/src/index.d.ts +7 -0
  81. package/src/io/_utils.d.cts +10 -0
  82. package/src/io/_utils.d.ts +10 -0
  83. package/src/io/buf-writer.d.cts +13 -0
  84. package/src/io/buf-writer.d.ts +13 -0
  85. package/src/io/io.d.cts +5 -0
  86. package/src/io/io.d.ts +5 -0
  87. package/src/io/readers.d.cts +199 -0
  88. package/src/io/readers.d.ts +199 -0
  89. package/src/io/writers.d.cts +22 -0
  90. package/src/io/writers.d.ts +22 -0
  91. package/src/types/agent.d.cts +128 -0
  92. package/src/types/agent.d.ts +128 -0
  93. package/src/types/dialer.d.cts +27 -0
  94. package/src/types/dialer.d.ts +27 -0
  95. package/src/types/index.d.cts +2 -0
  96. package/src/types/index.d.ts +2 -0
  97. package/tests/test-utils.d.cts +8 -0
  98. package/tests/test-utils.d.ts +8 -0
package/io/readers.cjs ADDED
@@ -0,0 +1,377 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_consts = require("../_internal/consts.cjs");
3
+ const require__utils = require("./_utils.cjs");
4
+ let _fuman_io = require("@fuman/io");
5
+ let _fuman_net = require("@fuman/net");
6
+ //#region src/io/readers.ts
7
+ var invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/g;
8
+ function sanitizeHeaderValue(v) {
9
+ return v.replace(invalidHeaderCharRegex, (m) => encodeURI(m));
10
+ }
11
+ /**
12
+ * CRLF-delimited reader that preserves over-reads in an internal buffer.
13
+ *
14
+ * Key property: once you finish reading headers, any bytes already read beyond
15
+ * the header terminator stay buffered and will be returned by read().
16
+ */
17
+ var LineReader = class {
18
+ #src;
19
+ #buf;
20
+ #codec = new _fuman_io.DelimiterCodec(require_consts.CRLF_BYTES, { strategy: "discard" });
21
+ #eof = false;
22
+ #highWaterMark;
23
+ #maxBufferedBytes;
24
+ #maxLineSize;
25
+ #closed = false;
26
+ close;
27
+ /**
28
+ * LineReader configuration.
29
+ *
30
+ * @namespace LineReader
31
+ */
32
+ static Options;
33
+ constructor(src, opts = {}) {
34
+ this.#src = src;
35
+ this.#buf = _fuman_io.Bytes.alloc(opts.bufferSize);
36
+ this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
37
+ this.#maxBufferedBytes = opts.maxBufferedBytes ?? 256 * 1024;
38
+ this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
39
+ this.close = this.#close.bind(this);
40
+ }
41
+ async read(into) {
42
+ if (this.#closed) return 0;
43
+ if (this.#buf.available > 0) {
44
+ const n = Math.min(into.length, this.#buf.available);
45
+ into.set(this.#buf.readSync(n));
46
+ this.#buf.reclaim();
47
+ return n;
48
+ }
49
+ if (this.#eof) return 0;
50
+ try {
51
+ return await this.#src.read(into);
52
+ } catch (e) {
53
+ if (e instanceof _fuman_net.ConnectionClosedError) {
54
+ this.#eof = true;
55
+ return 0;
56
+ }
57
+ throw e;
58
+ }
59
+ }
60
+ async readLine() {
61
+ if (this.#closed) return null;
62
+ for (;;) {
63
+ const frame = this.#codec.decode(this.#buf, this.#eof);
64
+ if (frame !== null) {
65
+ this.#buf.reclaim();
66
+ return _fuman_io.read.rawString(frame, frame.length);
67
+ }
68
+ if (this.#eof) return null;
69
+ if (this.#buf.available > this.#maxLineSize) throw new Error(`line too large (> ${this.#maxLineSize} bytes)`);
70
+ if (this.#buf.available > this.#maxBufferedBytes) throw new Error(`buffer too large while searching for delimiter (> ${this.#maxBufferedBytes} bytes)`);
71
+ await this.#pull();
72
+ }
73
+ }
74
+ async readHeaders(opts = {}) {
75
+ const maxHeaderSize = opts.maxHeaderSize ?? 64 * 1024;
76
+ const acc = /* @__PURE__ */ new Map();
77
+ const validator = new Headers();
78
+ let lastKey = null;
79
+ let firstLine = true;
80
+ let consumed = 0;
81
+ for (;;) {
82
+ const line = await this.readLine();
83
+ if (line === null) throw new Error("Unexpected EOF while reading HTTP headers");
84
+ consumed += line.length + 2;
85
+ if (consumed > maxHeaderSize) throw new Error(`HTTP headers too large (> ${maxHeaderSize} bytes)`);
86
+ if (line === "") break;
87
+ if (firstLine && (line[0] === " " || line[0] === " ")) throw new Error(`malformed HTTP header initial line: ${line}`);
88
+ firstLine = false;
89
+ if (line[0] === " " || line[0] === " ") {
90
+ if (!lastKey) throw new Error(`malformed HTTP header continuation line: ${line}`);
91
+ const arr = acc.get(lastKey);
92
+ if (!arr || arr.length === 0) throw new Error(`malformed HTTP header continuation line: ${line}`);
93
+ const piece = sanitizeHeaderValue(line.trim());
94
+ arr[arr.length - 1] = `${arr[arr.length - 1]} ${piece}`.trim();
95
+ continue;
96
+ }
97
+ const idx = line.indexOf(":");
98
+ if (idx === -1) throw new Error(`malformed HTTP header line: ${line}`);
99
+ const rawName = line.slice(0, idx).trim();
100
+ if (rawName === "") {
101
+ lastKey = null;
102
+ continue;
103
+ }
104
+ const name = rawName.toLowerCase();
105
+ const value = sanitizeHeaderValue(line.slice(idx + 1).trim());
106
+ try {
107
+ validator.append(name, value);
108
+ } catch {
109
+ lastKey = null;
110
+ continue;
111
+ }
112
+ const arr = acc.get(name);
113
+ if (arr) arr.push(value);
114
+ else acc.set(name, [value]);
115
+ lastKey = name;
116
+ }
117
+ const headers = new Headers();
118
+ for (const [k, values] of acc) for (const v of values) try {
119
+ headers.append(k, v);
120
+ } catch {}
121
+ return headers;
122
+ }
123
+ async #pull() {
124
+ const into = this.#buf.writeSync(this.#highWaterMark);
125
+ try {
126
+ const n = await this.#src.read(into);
127
+ this.#buf.disposeWriteSync(n);
128
+ if (n === 0) this.#eof = true;
129
+ } catch (e) {
130
+ this.#buf.disposeWriteSync(0);
131
+ if (e instanceof _fuman_net.ConnectionClosedError) {
132
+ this.#eof = true;
133
+ return;
134
+ }
135
+ throw e;
136
+ } finally {
137
+ this.#buf.reclaim();
138
+ }
139
+ }
140
+ async #close() {
141
+ if (this.#closed) return;
142
+ this.#closed = true;
143
+ try {
144
+ await this.#src.close();
145
+ } finally {
146
+ this.#closed = true;
147
+ }
148
+ }
149
+ };
150
+ /**
151
+ * Body reader that streams bytes with either a fixed Content-Length or until EOF.
152
+ */
153
+ var BodyReader = class {
154
+ #src;
155
+ #remaining;
156
+ #maxResponseSize;
157
+ #readSoFar = 0;
158
+ #closed = false;
159
+ close;
160
+ /**
161
+ * BodyReader configuration.
162
+ *
163
+ * @namespace BodyReader
164
+ */
165
+ static Options;
166
+ constructor(src, contentLength, opts = {}) {
167
+ this.#src = src;
168
+ this.#remaining = contentLength >= 0 ? contentLength : null;
169
+ this.#maxResponseSize = require__utils.parseMaxBytes(opts.maxBodySize);
170
+ if (this.#maxResponseSize != null && this.#remaining != null && this.#remaining > this.#maxResponseSize) throw new Error(`body too large: content-length=${this.#remaining} > maxResponseSize=${this.#maxResponseSize}`);
171
+ this.close = this.#close.bind(this);
172
+ }
173
+ async read(into) {
174
+ if (this.#closed) return 0;
175
+ if (this.#remaining === 0) return 0;
176
+ let max = into.length;
177
+ if (this.#remaining != null) max = Math.min(max, this.#remaining);
178
+ if (this.#maxResponseSize != null) {
179
+ const remainingLimit = this.#maxResponseSize - this.#readSoFar;
180
+ if (remainingLimit <= 0) throw new Error(`body too large (> ${this.#maxResponseSize} bytes)`);
181
+ max = Math.min(max, remainingLimit);
182
+ }
183
+ if (max === 0) return 0;
184
+ const view = max === into.length ? into : into.subarray(0, max);
185
+ let n = 0;
186
+ try {
187
+ n = await this.#src.read(view);
188
+ } catch (e) {
189
+ if (e instanceof _fuman_net.ConnectionClosedError) n = 0;
190
+ else throw e;
191
+ }
192
+ if (n === 0) {
193
+ if (this.#remaining != null) throw new Error("Unexpected EOF while reading fixed-length body");
194
+ return 0;
195
+ }
196
+ this.#readSoFar += n;
197
+ if (this.#remaining != null) this.#remaining -= n;
198
+ return n;
199
+ }
200
+ async #close() {
201
+ if (this.#closed) return;
202
+ this.#closed = true;
203
+ try {
204
+ await this.#src.close();
205
+ } finally {
206
+ this.#closed = true;
207
+ }
208
+ }
209
+ };
210
+ /**
211
+ * RFC 7230 chunked transfer-coding decoder.
212
+ */
213
+ var ChunkedBodyReader = class {
214
+ #src;
215
+ #buf;
216
+ #codec = new _fuman_io.DelimiterCodec(require_consts.CRLF_BYTES, { strategy: "discard" });
217
+ #highWaterMark;
218
+ #maxLineSize;
219
+ #maxChunkSize;
220
+ #maxResponseSize;
221
+ #readSoFar = 0;
222
+ #eof = false;
223
+ #closed = false;
224
+ #state = { kind: "size" };
225
+ close;
226
+ /**
227
+ * ChunkedBodyReader configuration.
228
+ *
229
+ * @namespace ChunkedBodyReader
230
+ */
231
+ static Options;
232
+ constructor(src, opts = {}) {
233
+ this.#src = src;
234
+ this.#buf = _fuman_io.Bytes.alloc(opts.bufferSize);
235
+ this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
236
+ this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
237
+ this.#maxChunkSize = opts.maxChunkSize ?? 16 * 1024 * 1024;
238
+ this.#maxResponseSize = require__utils.parseMaxBytes(opts.maxBodySize);
239
+ this.close = this.#close.bind(this);
240
+ }
241
+ async read(into) {
242
+ if (this.#closed) return 0;
243
+ for (;;) {
244
+ if (this.#state.kind === "done") return 0;
245
+ let view = into;
246
+ if (this.#maxResponseSize != null) {
247
+ const remainingLimit = this.#maxResponseSize - this.#readSoFar;
248
+ if (remainingLimit <= 0) throw new Error(`body too large (> ${this.#maxResponseSize} bytes)`);
249
+ if (view.length > remainingLimit) view = view.subarray(0, remainingLimit);
250
+ }
251
+ if (view.length === 0) return 0;
252
+ if (this.#state.kind === "data") {
253
+ if (this.#state.remaining === 0) {
254
+ this.#state = { kind: "crlf" };
255
+ continue;
256
+ }
257
+ if (this.#buf.available > 0) {
258
+ const n = Math.min(view.length, this.#state.remaining, this.#buf.available);
259
+ view.set(this.#buf.readSync(n));
260
+ this.#buf.reclaim();
261
+ this.#readSoFar += n;
262
+ this.#state = {
263
+ kind: "data",
264
+ remaining: this.#state.remaining - n
265
+ };
266
+ return n;
267
+ }
268
+ const max = Math.min(view.length, this.#state.remaining);
269
+ const slice = max === view.length ? view : view.subarray(0, max);
270
+ const n = await this.#readFromSrc(slice);
271
+ if (n === 0) throw new Error("Unexpected EOF while reading chunked body");
272
+ this.#readSoFar += n;
273
+ this.#state = {
274
+ kind: "data",
275
+ remaining: this.#state.remaining - n
276
+ };
277
+ return n;
278
+ }
279
+ if (this.#state.kind === "size") {
280
+ const line = await this.#readLine();
281
+ if (line === null) throw new Error("Unexpected EOF while reading chunk size");
282
+ const semi = line.indexOf(";");
283
+ const token = (semi === -1 ? line : line.slice(0, semi)).trim();
284
+ if (token === "") throw new Error(`invalid chunk size line: ${line}`);
285
+ const size = Number.parseInt(token, 16);
286
+ if (!Number.isFinite(size) || Number.isNaN(size) || size < 0) throw new Error(`invalid chunk size: ${token}`);
287
+ if (size > this.#maxChunkSize) throw new Error(`chunk too large (> ${this.#maxChunkSize} bytes)`);
288
+ if (this.#maxResponseSize != null) {
289
+ if (size > this.#maxResponseSize - this.#readSoFar) throw new Error(`body too large (> ${this.#maxResponseSize} bytes)`);
290
+ }
291
+ this.#state = size === 0 ? { kind: "trailers" } : {
292
+ kind: "data",
293
+ remaining: size
294
+ };
295
+ continue;
296
+ }
297
+ if (this.#state.kind === "crlf") {
298
+ await this.#consumeCrlf();
299
+ this.#state = { kind: "size" };
300
+ continue;
301
+ }
302
+ if (this.#state.kind === "trailers") for (;;) {
303
+ const line = await this.#readLine();
304
+ if (line === null) throw new Error("Unexpected EOF while reading chunked trailers");
305
+ if (line === "") {
306
+ this.#state = { kind: "done" };
307
+ return 0;
308
+ }
309
+ }
310
+ }
311
+ }
312
+ async #readFromSrc(into) {
313
+ if (this.#eof) return 0;
314
+ try {
315
+ const n = await this.#src.read(into);
316
+ if (n === 0) this.#eof = true;
317
+ return n;
318
+ } catch (e) {
319
+ if (e instanceof _fuman_net.ConnectionClosedError) {
320
+ this.#eof = true;
321
+ return 0;
322
+ }
323
+ throw e;
324
+ }
325
+ }
326
+ async #pull() {
327
+ const into = this.#buf.writeSync(this.#highWaterMark);
328
+ try {
329
+ const n = await this.#readFromSrc(into);
330
+ this.#buf.disposeWriteSync(n);
331
+ } catch (e) {
332
+ this.#buf.disposeWriteSync(0);
333
+ if (e instanceof _fuman_net.ConnectionClosedError) {
334
+ this.#eof = true;
335
+ return;
336
+ }
337
+ throw e;
338
+ } finally {
339
+ this.#buf.reclaim();
340
+ }
341
+ }
342
+ async #readLine() {
343
+ for (;;) {
344
+ const frame = this.#codec.decode(this.#buf, this.#eof);
345
+ if (frame !== null) {
346
+ this.#buf.reclaim();
347
+ return _fuman_io.read.rawString(frame, frame.length);
348
+ }
349
+ if (this.#eof) return null;
350
+ if (this.#buf.available > this.#maxLineSize) throw new Error(`chunk line too large (> ${this.#maxLineSize} bytes)`);
351
+ await this.#pull();
352
+ }
353
+ }
354
+ async #consumeCrlf() {
355
+ while (this.#buf.available < 2) {
356
+ if (this.#eof) throw new Error("Unexpected EOF while reading chunk terminator");
357
+ await this.#pull();
358
+ }
359
+ const two = this.#buf.readSync(2);
360
+ this.#buf.reclaim();
361
+ if (two[0] !== require_consts.CRLF_BYTES[0] || two[1] !== require_consts.CRLF_BYTES[1]) throw new Error("Invalid chunked encoding: missing CRLF after chunk data");
362
+ }
363
+ async #close() {
364
+ if (this.#closed) return;
365
+ this.#closed = true;
366
+ try {
367
+ await this.#src.close();
368
+ } finally {
369
+ this.#closed = true;
370
+ }
371
+ }
372
+ };
373
+ //#endregion
374
+ exports.BodyReader = BodyReader;
375
+ exports.ChunkedBodyReader = ChunkedBodyReader;
376
+ exports.LineReader = LineReader;
377
+ exports.sanitizeHeaderValue = sanitizeHeaderValue;