@npy/fetch 0.1.1 → 0.1.2

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 (109) hide show
  1. package/_internal/decode-stream-error.cjs +18 -0
  2. package/_internal/decode-stream-error.d.cts +11 -0
  3. package/_internal/decode-stream-error.d.ts +11 -0
  4. package/_internal/decode-stream-error.js +18 -0
  5. package/_internal/error-mapping.cjs +44 -0
  6. package/_internal/error-mapping.d.cts +15 -0
  7. package/_internal/error-mapping.d.ts +15 -0
  8. package/_internal/error-mapping.js +41 -0
  9. package/_internal/guards.cjs +5 -6
  10. package/{src/_internal → _internal}/guards.d.cts +2 -0
  11. package/{src/_internal → _internal}/guards.d.ts +2 -0
  12. package/_internal/guards.js +5 -7
  13. package/{src/_internal → _internal}/net.d.cts +1 -2
  14. package/{src/_internal → _internal}/net.d.ts +1 -2
  15. package/_internal/symbols.cjs +4 -0
  16. package/_internal/symbols.d.cts +1 -0
  17. package/_internal/symbols.d.ts +1 -0
  18. package/_internal/symbols.js +4 -0
  19. package/agent-pool.cjs +23 -5
  20. package/agent-pool.d.cts +2 -0
  21. package/agent-pool.d.ts +2 -0
  22. package/agent-pool.js +23 -5
  23. package/agent.cjs +17 -14
  24. package/agent.js +17 -14
  25. package/body.cjs +10 -59
  26. package/body.d.cts +12 -0
  27. package/body.d.ts +12 -0
  28. package/body.js +11 -60
  29. package/dialers/proxy.cjs +7 -0
  30. package/{src/dialers → dialers}/proxy.d.cts +11 -3
  31. package/{src/dialers → dialers}/proxy.d.ts +11 -3
  32. package/dialers/proxy.js +7 -0
  33. package/dialers/tcp.cjs +22 -0
  34. package/{src/dialers → dialers}/tcp.d.cts +23 -2
  35. package/{src/dialers → dialers}/tcp.d.ts +23 -2
  36. package/dialers/tcp.js +22 -0
  37. package/encoding.cjs +32 -13
  38. package/encoding.d.cts +35 -0
  39. package/encoding.d.ts +35 -0
  40. package/encoding.js +32 -13
  41. package/fetch.cjs +279 -43
  42. package/fetch.d.cts +58 -0
  43. package/fetch.d.ts +58 -0
  44. package/fetch.js +278 -43
  45. package/http-client.cjs +47 -5
  46. package/http-client.d.cts +39 -0
  47. package/http-client.d.ts +39 -0
  48. package/http-client.js +47 -5
  49. package/index.cjs +7 -3
  50. package/index.d.cts +14 -1
  51. package/index.d.ts +14 -1
  52. package/index.js +6 -4
  53. package/io/io.cjs +68 -4
  54. package/{src/io → io}/io.d.cts +1 -1
  55. package/{src/io → io}/io.d.ts +1 -1
  56. package/io/io.js +68 -4
  57. package/io/readers.cjs +14 -54
  58. package/io/readers.d.cts +69 -0
  59. package/io/readers.d.ts +69 -0
  60. package/io/readers.js +14 -54
  61. package/io/writers.cjs +10 -5
  62. package/{src/io → io}/writers.d.cts +1 -1
  63. package/{src/io → io}/writers.d.ts +1 -1
  64. package/io/writers.js +11 -6
  65. package/package.json +18 -2
  66. package/types/agent.d.cts +72 -0
  67. package/types/agent.d.ts +72 -0
  68. package/{src/types → types}/dialer.d.cts +3 -0
  69. package/{src/types → types}/dialer.d.ts +3 -0
  70. package/_internal/error-adapters.cjs +0 -146
  71. package/_internal/error-adapters.js +0 -142
  72. package/src/_internal/error-adapters.d.cts +0 -22
  73. package/src/_internal/error-adapters.d.ts +0 -22
  74. package/src/agent-pool.d.cts +0 -2
  75. package/src/agent-pool.d.ts +0 -2
  76. package/src/body.d.cts +0 -23
  77. package/src/body.d.ts +0 -23
  78. package/src/encoding.d.cts +0 -24
  79. package/src/encoding.d.ts +0 -24
  80. package/src/fetch.d.cts +0 -36
  81. package/src/fetch.d.ts +0 -36
  82. package/src/http-client.d.cts +0 -23
  83. package/src/http-client.d.ts +0 -23
  84. package/src/index.d.cts +0 -7
  85. package/src/index.d.ts +0 -7
  86. package/src/io/readers.d.cts +0 -199
  87. package/src/io/readers.d.ts +0 -199
  88. package/src/types/agent.d.cts +0 -128
  89. package/src/types/agent.d.ts +0 -128
  90. package/tests/test-utils.d.cts +0 -8
  91. package/tests/test-utils.d.ts +0 -8
  92. /package/{src/_internal → _internal}/consts.d.cts +0 -0
  93. /package/{src/_internal → _internal}/consts.d.ts +0 -0
  94. /package/{src/_internal → _internal}/promises.d.cts +0 -0
  95. /package/{src/_internal → _internal}/promises.d.ts +0 -0
  96. /package/{src/_internal → _internal}/streams.d.cts +0 -0
  97. /package/{src/_internal → _internal}/streams.d.ts +0 -0
  98. /package/{src/agent.d.cts → agent.d.cts} +0 -0
  99. /package/{src/agent.d.ts → agent.d.ts} +0 -0
  100. /package/{src/dialers → dialers}/index.d.cts +0 -0
  101. /package/{src/dialers → dialers}/index.d.ts +0 -0
  102. /package/{src/errors.d.cts → errors.d.cts} +0 -0
  103. /package/{src/errors.d.ts → errors.d.ts} +0 -0
  104. /package/{src/io → io}/_utils.d.cts +0 -0
  105. /package/{src/io → io}/_utils.d.ts +0 -0
  106. /package/{src/io → io}/buf-writer.d.cts +0 -0
  107. /package/{src/io → io}/buf-writer.d.ts +0 -0
  108. /package/{src/types → types}/index.d.cts +0 -0
  109. /package/{src/types → types}/index.d.ts +0 -0
package/io/io.cjs CHANGED
@@ -1,5 +1,5 @@
1
- const require_encoding = require("../encoding.cjs");
2
1
  const require_streams = require("../_internal/streams.cjs");
2
+ const require_encoding = require("../encoding.cjs");
3
3
  const require__utils = require("./_utils.cjs");
4
4
  const require_readers = require("./readers.cjs");
5
5
  const require_writers = require("./writers.cjs");
@@ -18,7 +18,70 @@ function parseStatusLine(line) {
18
18
  statusText: m[4] ?? ""
19
19
  };
20
20
  }
21
- async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
21
+ function toArrayBufferBytes(bytes) {
22
+ if (bytes.buffer instanceof ArrayBuffer) return bytes;
23
+ return new Uint8Array(bytes);
24
+ }
25
+ function wrapStreamErrors(source, mapError) {
26
+ const reader = source.getReader();
27
+ let pending = null;
28
+ return new ReadableStream({
29
+ type: "bytes",
30
+ async pull(controller) {
31
+ try {
32
+ const byob = controller.byobRequest;
33
+ if (byob?.view) {
34
+ const target = new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength);
35
+ if (target.byteLength === 0) {
36
+ byob.respond(0);
37
+ return;
38
+ }
39
+ let written = 0;
40
+ if (pending && pending.byteLength > 0) {
41
+ const n = Math.min(target.byteLength, pending.byteLength);
42
+ target.set(pending.subarray(0, n), written);
43
+ written += n;
44
+ pending = n === pending.byteLength ? null : pending.subarray(n);
45
+ }
46
+ while (written === 0) {
47
+ const { done, value } = await reader.read();
48
+ if (done) {
49
+ byob.respond(0);
50
+ controller.close();
51
+ return;
52
+ }
53
+ if (!value || value.byteLength === 0) continue;
54
+ const n = Math.min(target.byteLength - written, value.byteLength);
55
+ target.set(value.subarray(0, n), written);
56
+ written += n;
57
+ if (n < value.byteLength) pending = value.subarray(n);
58
+ }
59
+ byob.respond(written);
60
+ return;
61
+ }
62
+ if (pending && pending.byteLength > 0) {
63
+ const chunk = pending;
64
+ pending = null;
65
+ controller.enqueue(toArrayBufferBytes(chunk));
66
+ return;
67
+ }
68
+ const { done, value } = await reader.read();
69
+ if (done) {
70
+ controller.close();
71
+ return;
72
+ }
73
+ if (value && value.byteLength > 0) controller.enqueue(toArrayBufferBytes(value));
74
+ } catch (error) {
75
+ controller.error(mapError(error));
76
+ }
77
+ },
78
+ async cancel(reason) {
79
+ pending = null;
80
+ await reader.cancel(reason);
81
+ }
82
+ });
83
+ }
84
+ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone, mapBodyError) {
22
85
  const lr = new require_readers.LineReader(conn, options);
23
86
  const finalize = (() => {
24
87
  let called = false;
@@ -70,12 +133,12 @@ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
70
133
  });
71
134
  }
72
135
  if (chunked) headers.delete("content-length");
73
- const reader = chunked ? new require_readers.ChunkedBodyReader(lr, options) : new require_readers.BodyReader(lr, contentLength ?? -1, options);
136
+ const reader = chunked ? new require_readers.ChunkedBodyReader(lr, options) : new require_readers.BodyReader(lr, contentLength, options);
74
137
  let body = new ReadableStream({
75
138
  type: "bytes",
76
139
  async pull(controller) {
77
140
  const byob = controller.byobRequest;
78
- const view = byob?.view ? new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength) : new Uint8Array(new ArrayBuffer(options.highWaterMark ?? 16 * 1024));
141
+ const view = byob?.view ? new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength) : new Uint8Array(new ArrayBuffer(options.readChunkSize ?? 16 * 1024));
79
142
  try {
80
143
  const n = await reader.read(view);
81
144
  if (n === 0) {
@@ -117,6 +180,7 @@ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
117
180
  }
118
181
  const maxDecoded = require__utils.parseMaxBytes(options.maxDecodedBodySize);
119
182
  if (maxDecoded != null) body = body.pipeThrough(new require_streams.MaxBytesTransformStream(maxDecoded));
183
+ if (mapBodyError != null) body = wrapStreamErrors(body, mapBodyError);
120
184
  } catch (err) {
121
185
  finalize(false);
122
186
  throw err;
@@ -1,5 +1,5 @@
1
1
  import { IConnection } from '@fuman/net';
2
2
  import { LineReader, Readers } from './readers';
3
3
  import { Writers } from './writers';
4
- export declare function readResponse(conn: IConnection<unknown>, options: (Readers.Options & LineReader.ReadHeadersOptions) | undefined, shouldIgnoreBody: (status: number) => boolean, onDone?: (reusable: boolean) => void): Promise<Response>;
4
+ export declare function readResponse(conn: IConnection<unknown>, options: (Readers.Options & LineReader.ReadHeadersOptions) | undefined, shouldIgnoreBody: (status: number) => boolean, onDone?: (reusable: boolean) => void, mapBodyError?: (err: unknown) => unknown): Promise<Response>;
5
5
  export declare function writeRequest(conn: IConnection<unknown>, req: Writers.Request, options?: Writers.Options): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { IConnection } from '@fuman/net';
2
2
  import { LineReader, Readers } from './readers';
3
3
  import { Writers } from './writers';
4
- export declare function readResponse(conn: IConnection<unknown>, options: (Readers.Options & LineReader.ReadHeadersOptions) | undefined, shouldIgnoreBody: (status: number) => boolean, onDone?: (reusable: boolean) => void): Promise<Response>;
4
+ export declare function readResponse(conn: IConnection<unknown>, options: (Readers.Options & LineReader.ReadHeadersOptions) | undefined, shouldIgnoreBody: (status: number) => boolean, onDone?: (reusable: boolean) => void, mapBodyError?: (err: unknown) => unknown): Promise<Response>;
5
5
  export declare function writeRequest(conn: IConnection<unknown>, req: Writers.Request, options?: Writers.Options): Promise<void>;
package/io/io.js CHANGED
@@ -1,5 +1,5 @@
1
- import { decodeStream } from "../encoding.js";
2
1
  import { MaxBytesTransformStream } from "../_internal/streams.js";
2
+ import { decodeStream } from "../encoding.js";
3
3
  import { parseContentLength, parseMaxBytes, parseTransferEncoding, splitTokens } from "./_utils.js";
4
4
  import { BodyReader, ChunkedBodyReader, LineReader } from "./readers.js";
5
5
  import { createRequestWriter } from "./writers.js";
@@ -18,7 +18,70 @@ function parseStatusLine(line) {
18
18
  statusText: m[4] ?? ""
19
19
  };
20
20
  }
21
- async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
21
+ function toArrayBufferBytes(bytes) {
22
+ if (bytes.buffer instanceof ArrayBuffer) return bytes;
23
+ return new Uint8Array(bytes);
24
+ }
25
+ function wrapStreamErrors(source, mapError) {
26
+ const reader = source.getReader();
27
+ let pending = null;
28
+ return new ReadableStream({
29
+ type: "bytes",
30
+ async pull(controller) {
31
+ try {
32
+ const byob = controller.byobRequest;
33
+ if (byob?.view) {
34
+ const target = new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength);
35
+ if (target.byteLength === 0) {
36
+ byob.respond(0);
37
+ return;
38
+ }
39
+ let written = 0;
40
+ if (pending && pending.byteLength > 0) {
41
+ const n = Math.min(target.byteLength, pending.byteLength);
42
+ target.set(pending.subarray(0, n), written);
43
+ written += n;
44
+ pending = n === pending.byteLength ? null : pending.subarray(n);
45
+ }
46
+ while (written === 0) {
47
+ const { done, value } = await reader.read();
48
+ if (done) {
49
+ byob.respond(0);
50
+ controller.close();
51
+ return;
52
+ }
53
+ if (!value || value.byteLength === 0) continue;
54
+ const n = Math.min(target.byteLength - written, value.byteLength);
55
+ target.set(value.subarray(0, n), written);
56
+ written += n;
57
+ if (n < value.byteLength) pending = value.subarray(n);
58
+ }
59
+ byob.respond(written);
60
+ return;
61
+ }
62
+ if (pending && pending.byteLength > 0) {
63
+ const chunk = pending;
64
+ pending = null;
65
+ controller.enqueue(toArrayBufferBytes(chunk));
66
+ return;
67
+ }
68
+ const { done, value } = await reader.read();
69
+ if (done) {
70
+ controller.close();
71
+ return;
72
+ }
73
+ if (value && value.byteLength > 0) controller.enqueue(toArrayBufferBytes(value));
74
+ } catch (error) {
75
+ controller.error(mapError(error));
76
+ }
77
+ },
78
+ async cancel(reason) {
79
+ pending = null;
80
+ await reader.cancel(reason);
81
+ }
82
+ });
83
+ }
84
+ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone, mapBodyError) {
22
85
  const lr = new LineReader(conn, options);
23
86
  const finalize = (() => {
24
87
  let called = false;
@@ -70,12 +133,12 @@ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
70
133
  });
71
134
  }
72
135
  if (chunked) headers.delete("content-length");
73
- const reader = chunked ? new ChunkedBodyReader(lr, options) : new BodyReader(lr, contentLength ?? -1, options);
136
+ const reader = chunked ? new ChunkedBodyReader(lr, options) : new BodyReader(lr, contentLength, options);
74
137
  let body = new ReadableStream({
75
138
  type: "bytes",
76
139
  async pull(controller) {
77
140
  const byob = controller.byobRequest;
78
- const view = byob?.view ? new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength) : new Uint8Array(new ArrayBuffer(options.highWaterMark ?? 16 * 1024));
141
+ const view = byob?.view ? new Uint8Array(byob.view.buffer, byob.view.byteOffset, byob.view.byteLength) : new Uint8Array(new ArrayBuffer(options.readChunkSize ?? 16 * 1024));
79
142
  try {
80
143
  const n = await reader.read(view);
81
144
  if (n === 0) {
@@ -117,6 +180,7 @@ async function readResponse(conn, options = {}, shouldIgnoreBody, onDone) {
117
180
  }
118
181
  const maxDecoded = parseMaxBytes(options.maxDecodedBodySize);
119
182
  if (maxDecoded != null) body = body.pipeThrough(new MaxBytesTransformStream(maxDecoded));
183
+ if (mapBodyError != null) body = wrapStreamErrors(body, mapBodyError);
120
184
  } catch (err) {
121
185
  finalize(false);
122
186
  throw err;
package/io/readers.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  require("../_virtual/_rolldown/runtime.cjs");
2
- const require_consts = require("../_internal/consts.cjs");
3
2
  const require__utils = require("./_utils.cjs");
3
+ const require_consts = require("../_internal/consts.cjs");
4
4
  let _fuman_io = require("@fuman/io");
5
5
  let _fuman_net = require("@fuman/net");
6
6
  //#region src/io/readers.ts
@@ -8,32 +8,21 @@ var invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/g;
8
8
  function sanitizeHeaderValue(v) {
9
9
  return v.replace(invalidHeaderCharRegex, (m) => encodeURI(m));
10
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
11
  var LineReader = class {
18
12
  #src;
19
13
  #buf;
20
14
  #codec = new _fuman_io.DelimiterCodec(require_consts.CRLF_BYTES, { strategy: "discard" });
21
15
  #eof = false;
22
- #highWaterMark;
16
+ #readChunkSize;
23
17
  #maxBufferedBytes;
24
18
  #maxLineSize;
25
19
  #closed = false;
26
20
  close;
27
- /**
28
- * LineReader configuration.
29
- *
30
- * @namespace LineReader
31
- */
32
21
  static Options;
33
22
  constructor(src, opts = {}) {
34
23
  this.#src = src;
35
24
  this.#buf = _fuman_io.Bytes.alloc(opts.bufferSize);
36
- this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
25
+ this.#readChunkSize = opts.readChunkSize ?? 16 * 1024;
37
26
  this.#maxBufferedBytes = opts.maxBufferedBytes ?? 256 * 1024;
38
27
  this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
39
28
  this.close = this.#close.bind(this);
@@ -121,7 +110,7 @@ var LineReader = class {
121
110
  return headers;
122
111
  }
123
112
  async #pull() {
124
- const into = this.#buf.writeSync(this.#highWaterMark);
113
+ const into = this.#buf.writeSync(this.#readChunkSize);
125
114
  try {
126
115
  const n = await this.#src.read(into);
127
116
  this.#buf.disposeWriteSync(n);
@@ -140,16 +129,9 @@ var LineReader = class {
140
129
  async #close() {
141
130
  if (this.#closed) return;
142
131
  this.#closed = true;
143
- try {
144
- await this.#src.close();
145
- } finally {
146
- this.#closed = true;
147
- }
132
+ await this.#src.close();
148
133
  }
149
134
  };
150
- /**
151
- * Body reader that streams bytes with either a fixed Content-Length or until EOF.
152
- */
153
135
  var BodyReader = class {
154
136
  #src;
155
137
  #remaining;
@@ -157,29 +139,23 @@ var BodyReader = class {
157
139
  #readSoFar = 0;
158
140
  #closed = false;
159
141
  close;
160
- /**
161
- * BodyReader configuration.
162
- *
163
- * @namespace BodyReader
164
- */
165
142
  static Options;
166
143
  constructor(src, contentLength, opts = {}) {
167
144
  this.#src = src;
168
- this.#remaining = contentLength >= 0 ? contentLength : null;
145
+ this.#remaining = contentLength;
169
146
  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
147
  this.close = this.#close.bind(this);
172
148
  }
173
149
  async read(into) {
174
150
  if (this.#closed) return 0;
175
151
  if (this.#remaining === 0) return 0;
176
- let max = into.length;
177
- if (this.#remaining != null) max = Math.min(max, this.#remaining);
178
152
  if (this.#maxResponseSize != null) {
179
153
  const remainingLimit = this.#maxResponseSize - this.#readSoFar;
180
154
  if (remainingLimit <= 0) throw new Error(`body too large (> ${this.#maxResponseSize} bytes)`);
181
- max = Math.min(max, remainingLimit);
155
+ if (into.length > remainingLimit) into = into.subarray(0, remainingLimit);
182
156
  }
157
+ let max = into.length;
158
+ if (this.#remaining != null) max = Math.min(max, this.#remaining);
183
159
  if (max === 0) return 0;
184
160
  const view = max === into.length ? into : into.subarray(0, max);
185
161
  let n = 0;
@@ -200,21 +176,14 @@ var BodyReader = class {
200
176
  async #close() {
201
177
  if (this.#closed) return;
202
178
  this.#closed = true;
203
- try {
204
- await this.#src.close();
205
- } finally {
206
- this.#closed = true;
207
- }
179
+ await this.#src.close();
208
180
  }
209
181
  };
210
- /**
211
- * RFC 7230 chunked transfer-coding decoder.
212
- */
213
182
  var ChunkedBodyReader = class {
214
183
  #src;
215
184
  #buf;
216
185
  #codec = new _fuman_io.DelimiterCodec(require_consts.CRLF_BYTES, { strategy: "discard" });
217
- #highWaterMark;
186
+ #readChunkSize;
218
187
  #maxLineSize;
219
188
  #maxChunkSize;
220
189
  #maxResponseSize;
@@ -223,16 +192,11 @@ var ChunkedBodyReader = class {
223
192
  #closed = false;
224
193
  #state = { kind: "size" };
225
194
  close;
226
- /**
227
- * ChunkedBodyReader configuration.
228
- *
229
- * @namespace ChunkedBodyReader
230
- */
231
195
  static Options;
232
196
  constructor(src, opts = {}) {
233
197
  this.#src = src;
234
198
  this.#buf = _fuman_io.Bytes.alloc(opts.bufferSize);
235
- this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
199
+ this.#readChunkSize = opts.readChunkSize ?? 16 * 1024;
236
200
  this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
237
201
  this.#maxChunkSize = opts.maxChunkSize ?? 16 * 1024 * 1024;
238
202
  this.#maxResponseSize = require__utils.parseMaxBytes(opts.maxBodySize);
@@ -324,7 +288,7 @@ var ChunkedBodyReader = class {
324
288
  }
325
289
  }
326
290
  async #pull() {
327
- const into = this.#buf.writeSync(this.#highWaterMark);
291
+ const into = this.#buf.writeSync(this.#readChunkSize);
328
292
  try {
329
293
  const n = await this.#readFromSrc(into);
330
294
  this.#buf.disposeWriteSync(n);
@@ -363,11 +327,7 @@ var ChunkedBodyReader = class {
363
327
  async #close() {
364
328
  if (this.#closed) return;
365
329
  this.#closed = true;
366
- try {
367
- await this.#src.close();
368
- } finally {
369
- this.#closed = true;
370
- }
330
+ await this.#src.close();
371
331
  }
372
332
  };
373
333
  //#endregion
@@ -0,0 +1,69 @@
1
+ import { IClosable, IReadable } from '@fuman/io';
2
+ type Source = IReadable & IClosable;
3
+ export declare function sanitizeHeaderValue(v: string): string;
4
+ export declare namespace Readers {
5
+ interface BufferingOptions {
6
+ bufferSize?: number;
7
+ readChunkSize?: number;
8
+ }
9
+ interface SizeLimitOptions {
10
+ maxBodySize?: number | string;
11
+ maxDecodedBodySize?: number | string;
12
+ }
13
+ interface DecompressionOptions {
14
+ decompress?: boolean;
15
+ }
16
+ interface DelimiterLimitsOptions {
17
+ maxLineSize?: number;
18
+ maxBufferedBytes?: number;
19
+ }
20
+ interface BodyOptions extends SizeLimitOptions, DecompressionOptions {
21
+ }
22
+ interface ChunkedOptions extends BufferingOptions {
23
+ maxLineSize?: number;
24
+ maxChunkSize?: number;
25
+ }
26
+ type Options = LineReader.Options & BodyOptions & ChunkedOptions;
27
+ }
28
+ export declare class LineReader implements IReadable, IClosable {
29
+ #private;
30
+ close: () => Promise<void> | void;
31
+ static Options: never;
32
+ constructor(src: Source, opts?: LineReader.Options);
33
+ read(into: Uint8Array): Promise<number>;
34
+ readLine(): Promise<string | null>;
35
+ readHeaders(opts?: LineReader.ReadHeadersOptions): Promise<Headers>;
36
+ }
37
+ export declare namespace LineReader {
38
+ interface Options extends Readers.BufferingOptions, Readers.DelimiterLimitsOptions {
39
+ }
40
+ interface ReadHeadersOptions {
41
+ maxHeaderSize?: number;
42
+ }
43
+ }
44
+ export declare class BodyReader implements IReadable, IClosable {
45
+ #private;
46
+ close: () => Promise<void> | void;
47
+ static Options: never;
48
+ constructor(src: Source, contentLength: number | null, opts?: BodyReader.Options);
49
+ read(into: Uint8Array): Promise<number>;
50
+ }
51
+ export declare namespace BodyReader {
52
+ interface Options {
53
+ maxBodySize?: number | string;
54
+ }
55
+ }
56
+ export declare class ChunkedBodyReader implements IReadable, IClosable {
57
+ #private;
58
+ close: () => Promise<void> | void;
59
+ static Options: never;
60
+ constructor(src: Source, opts?: ChunkedBodyReader.Options);
61
+ read(into: Uint8Array): Promise<number>;
62
+ }
63
+ export declare namespace ChunkedBodyReader {
64
+ interface Options extends BodyReader.Options, Readers.BufferingOptions {
65
+ maxLineSize?: number;
66
+ maxChunkSize?: number;
67
+ }
68
+ }
69
+ export {};
@@ -0,0 +1,69 @@
1
+ import { IClosable, IReadable } from '@fuman/io';
2
+ type Source = IReadable & IClosable;
3
+ export declare function sanitizeHeaderValue(v: string): string;
4
+ export declare namespace Readers {
5
+ interface BufferingOptions {
6
+ bufferSize?: number;
7
+ readChunkSize?: number;
8
+ }
9
+ interface SizeLimitOptions {
10
+ maxBodySize?: number | string;
11
+ maxDecodedBodySize?: number | string;
12
+ }
13
+ interface DecompressionOptions {
14
+ decompress?: boolean;
15
+ }
16
+ interface DelimiterLimitsOptions {
17
+ maxLineSize?: number;
18
+ maxBufferedBytes?: number;
19
+ }
20
+ interface BodyOptions extends SizeLimitOptions, DecompressionOptions {
21
+ }
22
+ interface ChunkedOptions extends BufferingOptions {
23
+ maxLineSize?: number;
24
+ maxChunkSize?: number;
25
+ }
26
+ type Options = LineReader.Options & BodyOptions & ChunkedOptions;
27
+ }
28
+ export declare class LineReader implements IReadable, IClosable {
29
+ #private;
30
+ close: () => Promise<void> | void;
31
+ static Options: never;
32
+ constructor(src: Source, opts?: LineReader.Options);
33
+ read(into: Uint8Array): Promise<number>;
34
+ readLine(): Promise<string | null>;
35
+ readHeaders(opts?: LineReader.ReadHeadersOptions): Promise<Headers>;
36
+ }
37
+ export declare namespace LineReader {
38
+ interface Options extends Readers.BufferingOptions, Readers.DelimiterLimitsOptions {
39
+ }
40
+ interface ReadHeadersOptions {
41
+ maxHeaderSize?: number;
42
+ }
43
+ }
44
+ export declare class BodyReader implements IReadable, IClosable {
45
+ #private;
46
+ close: () => Promise<void> | void;
47
+ static Options: never;
48
+ constructor(src: Source, contentLength: number | null, opts?: BodyReader.Options);
49
+ read(into: Uint8Array): Promise<number>;
50
+ }
51
+ export declare namespace BodyReader {
52
+ interface Options {
53
+ maxBodySize?: number | string;
54
+ }
55
+ }
56
+ export declare class ChunkedBodyReader implements IReadable, IClosable {
57
+ #private;
58
+ close: () => Promise<void> | void;
59
+ static Options: never;
60
+ constructor(src: Source, opts?: ChunkedBodyReader.Options);
61
+ read(into: Uint8Array): Promise<number>;
62
+ }
63
+ export declare namespace ChunkedBodyReader {
64
+ interface Options extends BodyReader.Options, Readers.BufferingOptions {
65
+ maxLineSize?: number;
66
+ maxChunkSize?: number;
67
+ }
68
+ }
69
+ export {};
package/io/readers.js CHANGED
@@ -1,5 +1,5 @@
1
- import { CRLF_BYTES } from "../_internal/consts.js";
2
1
  import { parseMaxBytes } from "./_utils.js";
2
+ import { CRLF_BYTES } from "../_internal/consts.js";
3
3
  import { Bytes, DelimiterCodec, read } from "@fuman/io";
4
4
  import { ConnectionClosedError } from "@fuman/net";
5
5
  //#region src/io/readers.ts
@@ -7,32 +7,21 @@ var invalidHeaderCharRegex = /[^\t\x20-\x7e\x80-\xff]/g;
7
7
  function sanitizeHeaderValue(v) {
8
8
  return v.replace(invalidHeaderCharRegex, (m) => encodeURI(m));
9
9
  }
10
- /**
11
- * CRLF-delimited reader that preserves over-reads in an internal buffer.
12
- *
13
- * Key property: once you finish reading headers, any bytes already read beyond
14
- * the header terminator stay buffered and will be returned by read().
15
- */
16
10
  var LineReader = class {
17
11
  #src;
18
12
  #buf;
19
13
  #codec = new DelimiterCodec(CRLF_BYTES, { strategy: "discard" });
20
14
  #eof = false;
21
- #highWaterMark;
15
+ #readChunkSize;
22
16
  #maxBufferedBytes;
23
17
  #maxLineSize;
24
18
  #closed = false;
25
19
  close;
26
- /**
27
- * LineReader configuration.
28
- *
29
- * @namespace LineReader
30
- */
31
20
  static Options;
32
21
  constructor(src, opts = {}) {
33
22
  this.#src = src;
34
23
  this.#buf = Bytes.alloc(opts.bufferSize);
35
- this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
24
+ this.#readChunkSize = opts.readChunkSize ?? 16 * 1024;
36
25
  this.#maxBufferedBytes = opts.maxBufferedBytes ?? 256 * 1024;
37
26
  this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
38
27
  this.close = this.#close.bind(this);
@@ -120,7 +109,7 @@ var LineReader = class {
120
109
  return headers;
121
110
  }
122
111
  async #pull() {
123
- const into = this.#buf.writeSync(this.#highWaterMark);
112
+ const into = this.#buf.writeSync(this.#readChunkSize);
124
113
  try {
125
114
  const n = await this.#src.read(into);
126
115
  this.#buf.disposeWriteSync(n);
@@ -139,16 +128,9 @@ var LineReader = class {
139
128
  async #close() {
140
129
  if (this.#closed) return;
141
130
  this.#closed = true;
142
- try {
143
- await this.#src.close();
144
- } finally {
145
- this.#closed = true;
146
- }
131
+ await this.#src.close();
147
132
  }
148
133
  };
149
- /**
150
- * Body reader that streams bytes with either a fixed Content-Length or until EOF.
151
- */
152
134
  var BodyReader = class {
153
135
  #src;
154
136
  #remaining;
@@ -156,29 +138,23 @@ var BodyReader = class {
156
138
  #readSoFar = 0;
157
139
  #closed = false;
158
140
  close;
159
- /**
160
- * BodyReader configuration.
161
- *
162
- * @namespace BodyReader
163
- */
164
141
  static Options;
165
142
  constructor(src, contentLength, opts = {}) {
166
143
  this.#src = src;
167
- this.#remaining = contentLength >= 0 ? contentLength : null;
144
+ this.#remaining = contentLength;
168
145
  this.#maxResponseSize = parseMaxBytes(opts.maxBodySize);
169
- if (this.#maxResponseSize != null && this.#remaining != null && this.#remaining > this.#maxResponseSize) throw new Error(`body too large: content-length=${this.#remaining} > maxResponseSize=${this.#maxResponseSize}`);
170
146
  this.close = this.#close.bind(this);
171
147
  }
172
148
  async read(into) {
173
149
  if (this.#closed) return 0;
174
150
  if (this.#remaining === 0) return 0;
175
- let max = into.length;
176
- if (this.#remaining != null) max = Math.min(max, this.#remaining);
177
151
  if (this.#maxResponseSize != null) {
178
152
  const remainingLimit = this.#maxResponseSize - this.#readSoFar;
179
153
  if (remainingLimit <= 0) throw new Error(`body too large (> ${this.#maxResponseSize} bytes)`);
180
- max = Math.min(max, remainingLimit);
154
+ if (into.length > remainingLimit) into = into.subarray(0, remainingLimit);
181
155
  }
156
+ let max = into.length;
157
+ if (this.#remaining != null) max = Math.min(max, this.#remaining);
182
158
  if (max === 0) return 0;
183
159
  const view = max === into.length ? into : into.subarray(0, max);
184
160
  let n = 0;
@@ -199,21 +175,14 @@ var BodyReader = class {
199
175
  async #close() {
200
176
  if (this.#closed) return;
201
177
  this.#closed = true;
202
- try {
203
- await this.#src.close();
204
- } finally {
205
- this.#closed = true;
206
- }
178
+ await this.#src.close();
207
179
  }
208
180
  };
209
- /**
210
- * RFC 7230 chunked transfer-coding decoder.
211
- */
212
181
  var ChunkedBodyReader = class {
213
182
  #src;
214
183
  #buf;
215
184
  #codec = new DelimiterCodec(CRLF_BYTES, { strategy: "discard" });
216
- #highWaterMark;
185
+ #readChunkSize;
217
186
  #maxLineSize;
218
187
  #maxChunkSize;
219
188
  #maxResponseSize;
@@ -222,16 +191,11 @@ var ChunkedBodyReader = class {
222
191
  #closed = false;
223
192
  #state = { kind: "size" };
224
193
  close;
225
- /**
226
- * ChunkedBodyReader configuration.
227
- *
228
- * @namespace ChunkedBodyReader
229
- */
230
194
  static Options;
231
195
  constructor(src, opts = {}) {
232
196
  this.#src = src;
233
197
  this.#buf = Bytes.alloc(opts.bufferSize);
234
- this.#highWaterMark = opts.highWaterMark ?? 16 * 1024;
198
+ this.#readChunkSize = opts.readChunkSize ?? 16 * 1024;
235
199
  this.#maxLineSize = opts.maxLineSize ?? 64 * 1024;
236
200
  this.#maxChunkSize = opts.maxChunkSize ?? 16 * 1024 * 1024;
237
201
  this.#maxResponseSize = parseMaxBytes(opts.maxBodySize);
@@ -323,7 +287,7 @@ var ChunkedBodyReader = class {
323
287
  }
324
288
  }
325
289
  async #pull() {
326
- const into = this.#buf.writeSync(this.#highWaterMark);
290
+ const into = this.#buf.writeSync(this.#readChunkSize);
327
291
  try {
328
292
  const n = await this.#readFromSrc(into);
329
293
  this.#buf.disposeWriteSync(n);
@@ -362,11 +326,7 @@ var ChunkedBodyReader = class {
362
326
  async #close() {
363
327
  if (this.#closed) return;
364
328
  this.#closed = true;
365
- try {
366
- await this.#src.close();
367
- } finally {
368
- this.#closed = true;
369
- }
329
+ await this.#src.close();
370
330
  }
371
331
  };
372
332
  //#endregion