@npy/fetch 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/_internal/consts.cjs +4 -0
- package/_internal/consts.d.cts +3 -0
- package/_internal/consts.d.ts +3 -0
- package/_internal/consts.js +4 -0
- package/_internal/decode-stream-error.cjs +18 -0
- package/{src/_internal/decode-stream-error.ts → _internal/decode-stream-error.d.cts} +2 -7
- package/_internal/decode-stream-error.d.ts +11 -0
- package/_internal/decode-stream-error.js +18 -0
- package/_internal/error-mapping.cjs +44 -0
- package/_internal/error-mapping.d.cts +15 -0
- package/_internal/error-mapping.d.ts +15 -0
- package/_internal/error-mapping.js +41 -0
- package/_internal/guards.cjs +23 -0
- package/_internal/guards.d.cts +15 -0
- package/_internal/guards.d.ts +15 -0
- package/_internal/guards.js +15 -0
- package/_internal/net.cjs +95 -0
- package/_internal/net.d.cts +11 -0
- package/_internal/net.d.ts +11 -0
- package/_internal/net.js +92 -0
- package/_internal/promises.cjs +18 -0
- package/_internal/promises.d.cts +1 -0
- package/_internal/promises.d.ts +1 -0
- package/_internal/promises.js +18 -0
- package/_internal/streams.cjs +37 -0
- package/_internal/streams.d.cts +21 -0
- package/_internal/streams.d.ts +21 -0
- package/_internal/streams.js +36 -0
- package/_internal/symbols.cjs +4 -0
- package/_internal/symbols.d.cts +1 -0
- package/_internal/symbols.d.ts +1 -0
- package/_internal/symbols.js +4 -0
- package/_virtual/_rolldown/runtime.cjs +23 -0
- package/agent-pool.cjs +96 -0
- package/agent-pool.d.cts +2 -0
- package/agent-pool.d.ts +2 -0
- package/agent-pool.js +95 -0
- package/agent.cjs +260 -0
- package/agent.d.cts +3 -0
- package/agent.d.ts +3 -0
- package/agent.js +259 -0
- package/body.cjs +105 -0
- package/body.d.cts +12 -0
- package/body.d.ts +12 -0
- package/body.js +102 -0
- package/dialers/index.d.cts +3 -0
- package/dialers/index.d.ts +3 -0
- package/dialers/proxy.cjs +56 -0
- package/dialers/proxy.d.cts +27 -0
- package/dialers/proxy.d.ts +27 -0
- package/dialers/proxy.js +55 -0
- package/dialers/tcp.cjs +92 -0
- package/dialers/tcp.d.cts +57 -0
- package/dialers/tcp.d.ts +57 -0
- package/dialers/tcp.js +89 -0
- package/encoding.cjs +114 -0
- package/encoding.d.cts +35 -0
- package/encoding.d.ts +35 -0
- package/encoding.js +110 -0
- package/errors.cjs +275 -0
- package/errors.d.cts +110 -0
- package/errors.d.ts +110 -0
- package/errors.js +259 -0
- package/fetch.cjs +353 -0
- package/fetch.d.cts +58 -0
- package/fetch.d.ts +58 -0
- package/fetch.js +350 -0
- package/http-client.cjs +75 -0
- package/http-client.d.cts +39 -0
- package/http-client.d.ts +39 -0
- package/http-client.js +75 -0
- package/index.cjs +49 -0
- package/index.d.cts +14 -0
- package/index.d.ts +14 -0
- package/index.js +11 -0
- package/io/_utils.cjs +56 -0
- package/io/_utils.d.cts +10 -0
- package/io/_utils.d.ts +10 -0
- package/io/_utils.js +51 -0
- package/io/buf-writer.cjs +149 -0
- package/io/buf-writer.d.cts +13 -0
- package/io/buf-writer.d.ts +13 -0
- package/io/buf-writer.js +148 -0
- package/io/io.cjs +199 -0
- package/io/io.d.cts +5 -0
- package/io/io.d.ts +5 -0
- package/io/io.js +198 -0
- package/io/readers.cjs +337 -0
- package/io/readers.d.cts +69 -0
- package/io/readers.d.ts +69 -0
- package/io/readers.js +333 -0
- package/io/writers.cjs +196 -0
- package/io/writers.d.cts +22 -0
- package/io/writers.d.ts +22 -0
- package/io/writers.js +195 -0
- package/package.json +30 -25
- package/{src/types/agent.ts → types/agent.d.cts} +21 -47
- package/types/agent.d.ts +72 -0
- package/{src/types/dialer.ts → types/dialer.d.cts} +9 -19
- package/types/dialer.d.ts +30 -0
- package/types/index.d.cts +2 -0
- package/types/index.d.ts +2 -0
- package/bun.lock +0 -68
- package/examples/custom-proxy-client.ts +0 -32
- package/examples/http-client.ts +0 -47
- package/examples/proxy.ts +0 -16
- package/examples/simple.ts +0 -15
- package/src/_internal/consts.ts +0 -3
- package/src/_internal/error-mapping.ts +0 -160
- package/src/_internal/guards.ts +0 -78
- package/src/_internal/net.ts +0 -173
- package/src/_internal/promises.ts +0 -22
- package/src/_internal/streams.ts +0 -52
- package/src/_internal/symbols.ts +0 -1
- package/src/agent-pool.ts +0 -157
- package/src/agent.ts +0 -408
- package/src/body.ts +0 -179
- package/src/dialers/index.ts +0 -3
- package/src/dialers/proxy.ts +0 -102
- package/src/dialers/tcp.ts +0 -162
- package/src/encoding.ts +0 -222
- package/src/errors.ts +0 -357
- package/src/fetch.ts +0 -626
- package/src/http-client.ts +0 -111
- package/src/index.ts +0 -14
- package/src/io/_utils.ts +0 -82
- package/src/io/buf-writer.ts +0 -183
- package/src/io/io.ts +0 -322
- package/src/io/readers.ts +0 -576
- package/src/io/writers.ts +0 -331
- package/src/types/index.ts +0 -2
- package/tests/agent-pool.test.ts +0 -111
- package/tests/agent.test.ts +0 -134
- package/tests/body.test.ts +0 -228
- package/tests/errors.test.ts +0 -152
- package/tests/fetch.test.ts +0 -421
- package/tests/io-options.test.ts +0 -127
- package/tests/multipart.test.ts +0 -348
- package/tests/test-utils.ts +0 -335
- package/tsconfig.json +0 -15
package/src/io/writers.ts
DELETED
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
import type { ISyncWritable, IWritable } from "@fuman/io";
|
|
2
|
-
import { Bytes, fumanReadableToWeb, write as ioWrite } from "@fuman/io";
|
|
3
|
-
import { nodeReadableToWeb } from "@fuman/node";
|
|
4
|
-
import { CRLF_STR } from "../_internal/consts";
|
|
5
|
-
import { isFumanReadable, isReadableStream } from "../_internal/guards";
|
|
6
|
-
import { bytesToStream } from "../_internal/streams";
|
|
7
|
-
import { type BodyInit, extractBody } from "../body";
|
|
8
|
-
import { createEncoders, encodeStream } from "../encoding";
|
|
9
|
-
import { parseContentLength, parseTransferEncoding } from "./_utils";
|
|
10
|
-
import { BufWriter } from "./buf-writer";
|
|
11
|
-
import { sanitizeHeaderValue } from "./readers";
|
|
12
|
-
|
|
13
|
-
type Destination = IWritable;
|
|
14
|
-
type ByteStream = ReadableStream<Uint8Array>;
|
|
15
|
-
|
|
16
|
-
type PreparedBody =
|
|
17
|
-
| { kind: "none" }
|
|
18
|
-
| { kind: "bytes"; bytes: Uint8Array; length: number }
|
|
19
|
-
| { kind: "stream"; stream: ByteStream; length: number | null };
|
|
20
|
-
|
|
21
|
-
type RequestHead = {
|
|
22
|
-
method: string;
|
|
23
|
-
target: string;
|
|
24
|
-
headers: Headers;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export namespace Writers {
|
|
28
|
-
export interface Options {
|
|
29
|
-
writeBufferSize?: number;
|
|
30
|
-
directWriteThreshold?: number;
|
|
31
|
-
coalesceBodyMaxBytes?: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface Request {
|
|
35
|
-
url: URL;
|
|
36
|
-
method: string;
|
|
37
|
-
headers?: Headers;
|
|
38
|
-
body?: BodyInit | null;
|
|
39
|
-
signal?: AbortSignal;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface Writer {
|
|
43
|
-
write(req: Request): Promise<void>;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function toRequestTarget(url: URL): string {
|
|
48
|
-
const pathname = url.pathname?.startsWith("/") ? url.pathname : "/";
|
|
49
|
-
return pathname + (url.search || "");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function encodeHead(into: ISyncWritable, head: RequestHead): void {
|
|
53
|
-
ioWrite.rawString(
|
|
54
|
-
into,
|
|
55
|
-
`${head.method.toUpperCase()} ${head.target} HTTP/1.1${CRLF_STR}`,
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
for (const [k, v] of head.headers) {
|
|
59
|
-
ioWrite.rawString(into, `${k}: ${sanitizeHeaderValue(v)}${CRLF_STR}`);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
ioWrite.rawString(into, CRLF_STR);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function prepareBody(headers: Headers, init: BodyInit | null): PreparedBody {
|
|
66
|
-
const state = extractBody(init);
|
|
67
|
-
|
|
68
|
-
if (state.body != null && !headers.has("content-type")) {
|
|
69
|
-
headers.set(
|
|
70
|
-
"content-type",
|
|
71
|
-
state.contentType ?? "application/octet-stream",
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const body = state.body;
|
|
76
|
-
if (body == null) return { kind: "none" };
|
|
77
|
-
|
|
78
|
-
if (body instanceof Uint8Array) {
|
|
79
|
-
return { kind: "bytes", bytes: body, length: body.byteLength };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (isReadableStream(body)) {
|
|
83
|
-
return {
|
|
84
|
-
kind: "stream",
|
|
85
|
-
stream: body as ByteStream,
|
|
86
|
-
length: state.contentLength,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (isFumanReadable(body)) {
|
|
91
|
-
return {
|
|
92
|
-
kind: "stream",
|
|
93
|
-
stream: fumanReadableToWeb(body) as ByteStream,
|
|
94
|
-
length: state.contentLength,
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
kind: "stream",
|
|
100
|
-
stream: nodeReadableToWeb(body) as ByteStream,
|
|
101
|
-
length: state.contentLength,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function finalizeDelimitation(
|
|
106
|
-
headers: Headers,
|
|
107
|
-
body: PreparedBody,
|
|
108
|
-
): { chunked: boolean } {
|
|
109
|
-
if (body.kind === "none") return { chunked: false };
|
|
110
|
-
|
|
111
|
-
const te = parseTransferEncoding(headers);
|
|
112
|
-
if (te.has) {
|
|
113
|
-
headers.delete("content-length");
|
|
114
|
-
|
|
115
|
-
if (!te.chunked) {
|
|
116
|
-
const tokens = [...te.codings, "chunked"].filter(Boolean);
|
|
117
|
-
headers.set("transfer-encoding", tokens.join(", "));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return { chunked: true };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const knownLength =
|
|
124
|
-
body.kind === "bytes" ? body.length : (body.length ?? null);
|
|
125
|
-
if (knownLength != null) {
|
|
126
|
-
const existing = parseContentLength(headers);
|
|
127
|
-
if (existing != null && existing !== knownLength) {
|
|
128
|
-
throw new Error(
|
|
129
|
-
`Conflicting content-length: header=${existing} body=${knownLength}`,
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
if (existing == null) {
|
|
133
|
-
headers.set("content-length", String(knownLength));
|
|
134
|
-
}
|
|
135
|
-
return { chunked: false };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const existing = parseContentLength(headers);
|
|
139
|
-
if (existing != null) return { chunked: false };
|
|
140
|
-
|
|
141
|
-
headers.set("transfer-encoding", "chunked");
|
|
142
|
-
headers.delete("content-length");
|
|
143
|
-
return { chunked: true };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function createBufferedConnWriter(dst: Destination, opts: Writers.Options) {
|
|
147
|
-
const bufferSize = opts.writeBufferSize ?? 16 * 1024;
|
|
148
|
-
const directWriteThreshold = opts.directWriteThreshold ?? 64 * 1024;
|
|
149
|
-
const bufWriter = new BufWriter(dst, bufferSize);
|
|
150
|
-
|
|
151
|
-
const flush = async (): Promise<void> => {
|
|
152
|
-
await bufWriter.flush();
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
const writeBytes = async (bytes: Uint8Array): Promise<void> => {
|
|
156
|
-
if (bytes.length === 0) return;
|
|
157
|
-
|
|
158
|
-
if (bytes.length >= directWriteThreshold) {
|
|
159
|
-
await bufWriter.flush();
|
|
160
|
-
await dst.write(bytes);
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
await bufWriter.write(bytes);
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const writeRawString = async (str: string): Promise<void> => {
|
|
168
|
-
if (str.length === 0) return;
|
|
169
|
-
ioWrite.rawString(bufWriter, str);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
return { flush, writeBytes, writeRawString, directWriteThreshold };
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
async function writeBody(
|
|
176
|
-
dst: Destination,
|
|
177
|
-
body: Exclude<PreparedBody, { kind: "none" }>,
|
|
178
|
-
chunked: boolean,
|
|
179
|
-
opts: Writers.Options,
|
|
180
|
-
signal?: AbortSignal,
|
|
181
|
-
): Promise<void> {
|
|
182
|
-
const bw = createBufferedConnWriter(dst, opts);
|
|
183
|
-
|
|
184
|
-
const writeChunk = async (chunk: Uint8Array) => {
|
|
185
|
-
if (signal?.aborted)
|
|
186
|
-
throw signal.reason ?? new Error("Request aborted");
|
|
187
|
-
|
|
188
|
-
if (!chunked) {
|
|
189
|
-
await bw.writeBytes(chunk);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (chunk.length === 0) return;
|
|
194
|
-
|
|
195
|
-
await bw.writeRawString(chunk.length.toString(16));
|
|
196
|
-
await bw.writeRawString(CRLF_STR);
|
|
197
|
-
await bw.writeBytes(chunk);
|
|
198
|
-
await bw.writeRawString(CRLF_STR);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
if (body.kind === "bytes") {
|
|
202
|
-
await writeChunk(body.bytes);
|
|
203
|
-
} else {
|
|
204
|
-
for await (const chunk of body.stream) {
|
|
205
|
-
await writeChunk(chunk);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (chunked) {
|
|
210
|
-
await bw.writeRawString(`0${CRLF_STR}${CRLF_STR}`);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
await bw.flush();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
async function writeCoalesced(
|
|
217
|
-
dst: Destination,
|
|
218
|
-
scratch: Bytes,
|
|
219
|
-
head: RequestHead,
|
|
220
|
-
bodyBytes: Uint8Array,
|
|
221
|
-
chunked: boolean,
|
|
222
|
-
): Promise<void> {
|
|
223
|
-
scratch.reset();
|
|
224
|
-
encodeHead(scratch, head);
|
|
225
|
-
|
|
226
|
-
if (!chunked) {
|
|
227
|
-
ioWrite.bytes(scratch, bodyBytes);
|
|
228
|
-
await dst.write(scratch.result());
|
|
229
|
-
scratch.reset();
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
ioWrite.rawString(scratch, bodyBytes.length.toString(16));
|
|
234
|
-
ioWrite.rawString(scratch, CRLF_STR);
|
|
235
|
-
ioWrite.bytes(scratch, bodyBytes);
|
|
236
|
-
ioWrite.rawString(scratch, `${CRLF_STR}0${CRLF_STR}${CRLF_STR}`);
|
|
237
|
-
|
|
238
|
-
await dst.write(scratch.result());
|
|
239
|
-
scratch.reset();
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function createRequestWriter(
|
|
243
|
-
dst: Destination,
|
|
244
|
-
opts: Writers.Options = {},
|
|
245
|
-
): Writers.Writer {
|
|
246
|
-
const scratch = Bytes.alloc(opts.writeBufferSize ?? 16 * 1024);
|
|
247
|
-
|
|
248
|
-
const write = async (req: Writers.Request): Promise<void> => {
|
|
249
|
-
if (req.signal?.aborted)
|
|
250
|
-
throw req.signal.reason ?? new Error("Request aborted");
|
|
251
|
-
|
|
252
|
-
const method = req.method.toUpperCase();
|
|
253
|
-
const headers = req.headers ? new Headers(req.headers) : new Headers();
|
|
254
|
-
const url = req.url;
|
|
255
|
-
|
|
256
|
-
if (!headers.has("host")) headers.set("host", url.host);
|
|
257
|
-
if (!headers.has("date")) headers.set("date", new Date().toUTCString());
|
|
258
|
-
|
|
259
|
-
const target = toRequestTarget(url);
|
|
260
|
-
|
|
261
|
-
let body = prepareBody(headers, req.body ?? null);
|
|
262
|
-
|
|
263
|
-
const ceRaw = headers.get("content-encoding") ?? undefined;
|
|
264
|
-
const ceEncoders = createEncoders(ceRaw);
|
|
265
|
-
|
|
266
|
-
if (body.kind !== "none" && ceEncoders.length > 0) {
|
|
267
|
-
const stream =
|
|
268
|
-
body.kind === "stream"
|
|
269
|
-
? body.stream
|
|
270
|
-
: bytesToStream(body.bytes);
|
|
271
|
-
|
|
272
|
-
body = {
|
|
273
|
-
kind: "stream",
|
|
274
|
-
stream: encodeStream(stream, ceRaw) as ByteStream,
|
|
275
|
-
length: null,
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
headers.delete("content-length");
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const teInfo = parseTransferEncoding(headers);
|
|
282
|
-
if (body.kind !== "none" && teInfo.has && teInfo.codings.length > 0) {
|
|
283
|
-
const stream =
|
|
284
|
-
body.kind === "stream"
|
|
285
|
-
? body.stream
|
|
286
|
-
: bytesToStream(body.bytes);
|
|
287
|
-
|
|
288
|
-
body = {
|
|
289
|
-
kind: "stream",
|
|
290
|
-
stream: encodeStream(stream, teInfo.codings) as ByteStream,
|
|
291
|
-
length: null,
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
headers.delete("content-length");
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const { chunked } = finalizeDelimitation(headers, body);
|
|
298
|
-
|
|
299
|
-
const head: RequestHead = {
|
|
300
|
-
method,
|
|
301
|
-
target,
|
|
302
|
-
headers,
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
if (body.kind === "bytes") {
|
|
306
|
-
const max = opts.coalesceBodyMaxBytes ?? 64 * 1024;
|
|
307
|
-
if (body.bytes.length <= max) {
|
|
308
|
-
await writeCoalesced(dst, scratch, head, body.bytes, chunked);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
scratch.reset();
|
|
314
|
-
encodeHead(scratch, head);
|
|
315
|
-
await dst.write(scratch.result());
|
|
316
|
-
scratch.reset();
|
|
317
|
-
|
|
318
|
-
if (body.kind === "none") return;
|
|
319
|
-
|
|
320
|
-
if (body.kind === "bytes" && !chunked) {
|
|
321
|
-
if (req.signal?.aborted)
|
|
322
|
-
throw req.signal.reason ?? new Error("Request aborted");
|
|
323
|
-
await dst.write(body.bytes);
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await writeBody(dst, body, chunked, opts, req.signal);
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
return { write };
|
|
331
|
-
}
|
package/src/types/index.ts
DELETED
package/tests/agent-pool.test.ts
DELETED
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import { createAgentPool } from "../src/agent-pool";
|
|
3
|
-
import { RequestAbortedError } from "../src/errors";
|
|
4
|
-
import { createTestServer } from "./test-utils";
|
|
5
|
-
|
|
6
|
-
describe("agent-pool.ts", () => {
|
|
7
|
-
const testServer = createTestServer();
|
|
8
|
-
|
|
9
|
-
afterAll(async () => {
|
|
10
|
-
await testServer.stop();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
test("pool handles concurrent requests successfully", async () => {
|
|
14
|
-
const pool = createAgentPool(testServer.baseUrl, {
|
|
15
|
-
poolMaxPerHost: 4,
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
const requests = Array.from({ length: 8 }, (_, index) =>
|
|
20
|
-
pool
|
|
21
|
-
.send({
|
|
22
|
-
url: `${testServer.baseUrl}/echo`,
|
|
23
|
-
method: "POST",
|
|
24
|
-
headers: new Headers({
|
|
25
|
-
"content-type": "application/json",
|
|
26
|
-
}),
|
|
27
|
-
body: JSON.stringify({ index }),
|
|
28
|
-
})
|
|
29
|
-
.then((response) => response.json()),
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
const results = await Promise.all(requests);
|
|
33
|
-
|
|
34
|
-
expect(results).toHaveLength(8);
|
|
35
|
-
for (const result of results) {
|
|
36
|
-
expect(result.method).toBe("POST");
|
|
37
|
-
}
|
|
38
|
-
} finally {
|
|
39
|
-
await pool.close();
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("pool queues requests when poolMaxPerHost is small", async () => {
|
|
44
|
-
const pool = createAgentPool(testServer.baseUrl, {
|
|
45
|
-
poolMaxPerHost: 2,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const startedAt = performance.now();
|
|
50
|
-
|
|
51
|
-
const requests = Array.from({ length: 4 }, () =>
|
|
52
|
-
pool
|
|
53
|
-
.send({
|
|
54
|
-
url: `${testServer.baseUrl}/slow`,
|
|
55
|
-
method: "GET",
|
|
56
|
-
})
|
|
57
|
-
.then((response) => response.text()),
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const results = await Promise.all(requests);
|
|
61
|
-
const elapsed = performance.now() - startedAt;
|
|
62
|
-
|
|
63
|
-
expect(results).toEqual([
|
|
64
|
-
"Finally!",
|
|
65
|
-
"Finally!",
|
|
66
|
-
"Finally!",
|
|
67
|
-
"Finally!",
|
|
68
|
-
]);
|
|
69
|
-
|
|
70
|
-
expect(elapsed).toBeGreaterThanOrEqual(300);
|
|
71
|
-
} finally {
|
|
72
|
-
await pool.close();
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
test("pool propagates abort signals as RequestAbortedError", async () => {
|
|
77
|
-
const pool = createAgentPool(testServer.baseUrl, {
|
|
78
|
-
poolMaxPerHost: 2,
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const controller = new AbortController();
|
|
83
|
-
|
|
84
|
-
const request = pool.send({
|
|
85
|
-
url: `${testServer.baseUrl}/slow`,
|
|
86
|
-
method: "GET",
|
|
87
|
-
signal: controller.signal,
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
setTimeout(() => controller.abort(new Error("abort test")), 50);
|
|
91
|
-
|
|
92
|
-
await expect(request).rejects.toBeInstanceOf(RequestAbortedError);
|
|
93
|
-
} finally {
|
|
94
|
-
await pool.close();
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
test("pool closes cleanly via close()", async () => {
|
|
99
|
-
const pool = createAgentPool(testServer.baseUrl, {
|
|
100
|
-
poolMaxPerHost: 2,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const response = await pool.send({
|
|
104
|
-
url: `${testServer.baseUrl}/text`,
|
|
105
|
-
method: "GET",
|
|
106
|
-
});
|
|
107
|
-
expect(await response.text()).toBe("Hello, World!");
|
|
108
|
-
|
|
109
|
-
await expect(pool.close()).resolves.toBeUndefined();
|
|
110
|
-
});
|
|
111
|
-
});
|
package/tests/agent.test.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import { createAgent } from "../src/agent";
|
|
3
|
-
import { AutoDialer } from "../src/dialers";
|
|
4
|
-
import {
|
|
5
|
-
AgentBusyError,
|
|
6
|
-
OriginMismatchError,
|
|
7
|
-
RequestAbortedError,
|
|
8
|
-
ResponseDecodeError,
|
|
9
|
-
} from "../src/errors";
|
|
10
|
-
import { createTestServer } from "./test-utils";
|
|
11
|
-
|
|
12
|
-
describe("agent.ts", () => {
|
|
13
|
-
const testServer = createTestServer();
|
|
14
|
-
const dialer = new AutoDialer();
|
|
15
|
-
|
|
16
|
-
afterAll(async () => {
|
|
17
|
-
await testServer.stop();
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("agent performs sequential requests against the same origin", async () => {
|
|
21
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
const response1 = await agent.send({
|
|
25
|
-
url: `${testServer.baseUrl}/text`,
|
|
26
|
-
method: "GET",
|
|
27
|
-
});
|
|
28
|
-
expect(response1.status).toBe(200);
|
|
29
|
-
expect(await response1.text()).toBe("Hello, World!");
|
|
30
|
-
|
|
31
|
-
const response2 = await agent.send({
|
|
32
|
-
url: `${testServer.baseUrl}/json`,
|
|
33
|
-
method: "GET",
|
|
34
|
-
});
|
|
35
|
-
expect(response2.status).toBe(200);
|
|
36
|
-
expect(await response2.json()).toEqual({
|
|
37
|
-
message: "Hello, JSON!",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
expect(agent.isIdle).toBe(true);
|
|
41
|
-
} finally {
|
|
42
|
-
agent.close();
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("agent rejects cross-origin requests with OriginMismatchError", async () => {
|
|
47
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
await expect(
|
|
51
|
-
agent.send({
|
|
52
|
-
url: "http://example.com/test",
|
|
53
|
-
method: "GET",
|
|
54
|
-
}),
|
|
55
|
-
).rejects.toBeInstanceOf(OriginMismatchError);
|
|
56
|
-
} finally {
|
|
57
|
-
agent.close();
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("agent rejects concurrent use while busy with AgentBusyError", async () => {
|
|
62
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const slowRequest = agent.send({
|
|
66
|
-
url: `${testServer.baseUrl}/slow`,
|
|
67
|
-
method: "GET",
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
await expect(
|
|
71
|
-
agent.send({
|
|
72
|
-
url: `${testServer.baseUrl}/text`,
|
|
73
|
-
method: "GET",
|
|
74
|
-
}),
|
|
75
|
-
).rejects.toBeInstanceOf(AgentBusyError);
|
|
76
|
-
|
|
77
|
-
const response = await slowRequest;
|
|
78
|
-
expect(await response.text()).toBe("Finally!");
|
|
79
|
-
expect(agent.isIdle).toBe(true);
|
|
80
|
-
} finally {
|
|
81
|
-
agent.close();
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
test("agent returns to idle after aborted requests", async () => {
|
|
86
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const controller = new AbortController();
|
|
90
|
-
const request = agent.send({
|
|
91
|
-
url: `${testServer.baseUrl}/slow`,
|
|
92
|
-
method: "GET",
|
|
93
|
-
signal: controller.signal,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
setTimeout(() => controller.abort(new Error("abort test")), 50);
|
|
97
|
-
|
|
98
|
-
await expect(request).rejects.toBeInstanceOf(RequestAbortedError);
|
|
99
|
-
await expect(agent.whenIdle()).resolves.toBeUndefined();
|
|
100
|
-
expect(agent.isIdle).toBe(true);
|
|
101
|
-
} finally {
|
|
102
|
-
agent.close();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test("agent maps decoding failures during body consumption", async () => {
|
|
107
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
108
|
-
|
|
109
|
-
try {
|
|
110
|
-
const response = await agent.send({
|
|
111
|
-
url: `${testServer.baseUrl}/bad-gzip`,
|
|
112
|
-
method: "GET",
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
await expect(response.text()).rejects.toBeInstanceOf(
|
|
116
|
-
ResponseDecodeError,
|
|
117
|
-
);
|
|
118
|
-
} finally {
|
|
119
|
-
agent.close();
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
test("agent metadata reflects host and port", () => {
|
|
124
|
-
const agent = createAgent(dialer, testServer.baseUrl);
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const baseUrl = new URL(testServer.baseUrl);
|
|
128
|
-
expect(agent.hostname).toBe(baseUrl.hostname);
|
|
129
|
-
expect(agent.port).toBe(Number(baseUrl.port));
|
|
130
|
-
} finally {
|
|
131
|
-
agent.close();
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
});
|