@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/tests/multipart.test.ts
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { afterAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import { createFetch } from "../src/fetch";
|
|
3
|
-
import { createTestServer } from "./test-utils";
|
|
4
|
-
|
|
5
|
-
interface MultipartEchoResponse {
|
|
6
|
-
method: string;
|
|
7
|
-
contentType: string;
|
|
8
|
-
contentLength: number | null;
|
|
9
|
-
fields: Record<
|
|
10
|
-
string,
|
|
11
|
-
| string
|
|
12
|
-
| string[]
|
|
13
|
-
| {
|
|
14
|
-
type: "file";
|
|
15
|
-
filename: string;
|
|
16
|
-
mimeType: string;
|
|
17
|
-
size: number;
|
|
18
|
-
content: string;
|
|
19
|
-
}
|
|
20
|
-
>;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
describe("multipart/form-data — integration", () => {
|
|
24
|
-
const testServer = createTestServer();
|
|
25
|
-
|
|
26
|
-
afterAll(async () => {
|
|
27
|
-
await testServer.stop();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
test("Content-Type is multipart/form-data and contains a boundary", async () => {
|
|
31
|
-
const fetchLike = createFetch();
|
|
32
|
-
|
|
33
|
-
try {
|
|
34
|
-
const form = new FormData();
|
|
35
|
-
form.append("x", "1");
|
|
36
|
-
|
|
37
|
-
const response = await fetchLike(
|
|
38
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
39
|
-
{ method: "POST", body: form },
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
expect(response.status).toBe(200);
|
|
43
|
-
|
|
44
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
45
|
-
expect(echo.contentType).toContain("multipart/form-data");
|
|
46
|
-
expect(echo.contentType).toMatch(/boundary=[^\s;]+/);
|
|
47
|
-
} finally {
|
|
48
|
-
await fetchLike.close();
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test("Content-Length is sent and matches the body received by the server", async () => {
|
|
53
|
-
const fetchLike = createFetch();
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
const form = new FormData();
|
|
57
|
-
form.append("field", "some value");
|
|
58
|
-
|
|
59
|
-
const response = await fetchLike(
|
|
60
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
61
|
-
{ method: "POST", body: form },
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
expect(response.status).toBe(200);
|
|
65
|
-
|
|
66
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
67
|
-
|
|
68
|
-
expect(echo.contentLength).not.toBeNull();
|
|
69
|
-
expect(echo.contentLength).toBeGreaterThan(0);
|
|
70
|
-
} finally {
|
|
71
|
-
await fetchLike.close();
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("single text field is received correctly", async () => {
|
|
76
|
-
const fetchLike = createFetch();
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const form = new FormData();
|
|
80
|
-
form.append("greeting", "hello world");
|
|
81
|
-
|
|
82
|
-
const response = await fetchLike(
|
|
83
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
84
|
-
{ method: "POST", body: form },
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
88
|
-
expect(echo.fields["greeting"]).toBe("hello world");
|
|
89
|
-
} finally {
|
|
90
|
-
await fetchLike.close();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
test("multiple text fields are all received", async () => {
|
|
95
|
-
const fetchLike = createFetch();
|
|
96
|
-
|
|
97
|
-
try {
|
|
98
|
-
const form = new FormData();
|
|
99
|
-
form.append("first", "Alice");
|
|
100
|
-
form.append("last", "Smith");
|
|
101
|
-
form.append("age", "30");
|
|
102
|
-
|
|
103
|
-
const response = await fetchLike(
|
|
104
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
105
|
-
{ method: "POST", body: form },
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
109
|
-
expect(echo.fields["first"]).toBe("Alice");
|
|
110
|
-
expect(echo.fields["last"]).toBe("Smith");
|
|
111
|
-
expect(echo.fields["age"]).toBe("30");
|
|
112
|
-
} finally {
|
|
113
|
-
await fetchLike.close();
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("multiple values for the same field name are received as an array", async () => {
|
|
118
|
-
const fetchLike = createFetch();
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const form = new FormData();
|
|
122
|
-
form.append("tag", "typescript");
|
|
123
|
-
form.append("tag", "http");
|
|
124
|
-
form.append("tag", "fetch");
|
|
125
|
-
|
|
126
|
-
const response = await fetchLike(
|
|
127
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
128
|
-
{ method: "POST", body: form },
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
132
|
-
expect(echo.fields["tag"]).toEqual(["typescript", "http", "fetch"]);
|
|
133
|
-
} finally {
|
|
134
|
-
await fetchLike.close();
|
|
135
|
-
}
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("multibyte UTF-8 field values are received correctly", async () => {
|
|
139
|
-
const fetchLike = createFetch();
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
const form = new FormData();
|
|
143
|
-
form.append("greeting", "こんにちは");
|
|
144
|
-
form.append("emoji", "🚀");
|
|
145
|
-
|
|
146
|
-
const response = await fetchLike(
|
|
147
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
148
|
-
{ method: "POST", body: form },
|
|
149
|
-
);
|
|
150
|
-
|
|
151
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
152
|
-
expect(echo.fields["greeting"]).toBe("こんにちは");
|
|
153
|
-
expect(echo.fields["emoji"]).toBe("🚀");
|
|
154
|
-
} finally {
|
|
155
|
-
await fetchLike.close();
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("Blob field content is received intact", async () => {
|
|
160
|
-
const fetchLike = createFetch();
|
|
161
|
-
|
|
162
|
-
try {
|
|
163
|
-
const content = "hello from blob";
|
|
164
|
-
const form = new FormData();
|
|
165
|
-
form.append(
|
|
166
|
-
"upload",
|
|
167
|
-
new Blob([content], { type: "text/plain" }),
|
|
168
|
-
"hello.txt",
|
|
169
|
-
);
|
|
170
|
-
|
|
171
|
-
const response = await fetchLike(
|
|
172
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
173
|
-
{ method: "POST", body: form },
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
177
|
-
const file = echo.fields["upload"] as {
|
|
178
|
-
type: string;
|
|
179
|
-
filename: string;
|
|
180
|
-
mimeType: string;
|
|
181
|
-
content: string;
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
expect(file.filename).toBe("hello.txt");
|
|
185
|
-
expect(file.mimeType).toContain("text/plain");
|
|
186
|
-
expect(file.content).toBe(content);
|
|
187
|
-
} finally {
|
|
188
|
-
await fetchLike.close();
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
test("binary Blob field is received with correct byte count", async () => {
|
|
193
|
-
const fetchLike = createFetch();
|
|
194
|
-
|
|
195
|
-
try {
|
|
196
|
-
const bytes = new Uint8Array(256).map((_, i) => i);
|
|
197
|
-
const form = new FormData();
|
|
198
|
-
form.append(
|
|
199
|
-
"bin",
|
|
200
|
-
new Blob([bytes], { type: "application/octet-stream" }),
|
|
201
|
-
"data.bin",
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
const response = await fetchLike(
|
|
205
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
206
|
-
{ method: "POST", body: form },
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
210
|
-
const file = echo.fields["bin"] as {
|
|
211
|
-
size: number;
|
|
212
|
-
mimeType: string;
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
expect(file.size).toBe(256);
|
|
216
|
-
expect(file.mimeType).toBe("application/octet-stream");
|
|
217
|
-
} finally {
|
|
218
|
-
await fetchLike.close();
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test("Blob without explicit MIME type is received with correct content", async () => {
|
|
223
|
-
const fetchLike = createFetch();
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const form = new FormData();
|
|
227
|
-
form.append("f", new Blob(["data"]), "file.dat");
|
|
228
|
-
|
|
229
|
-
const response = await fetchLike(
|
|
230
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
231
|
-
{ method: "POST", body: form },
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
235
|
-
const file = echo.fields["f"] as { size: number; content: string };
|
|
236
|
-
expect(file.size).toBe(4);
|
|
237
|
-
expect(file.content).toBe("data");
|
|
238
|
-
} finally {
|
|
239
|
-
await fetchLike.close();
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
test("mixed text and Blob fields are all received correctly", async () => {
|
|
244
|
-
const fetchLike = createFetch();
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const form = new FormData();
|
|
248
|
-
form.append("title", "My Upload");
|
|
249
|
-
form.append(
|
|
250
|
-
"file",
|
|
251
|
-
new Blob(["<html><body>hi</body></html>"], {
|
|
252
|
-
type: "text/html",
|
|
253
|
-
}),
|
|
254
|
-
"index.html",
|
|
255
|
-
);
|
|
256
|
-
form.append("note", "optional note");
|
|
257
|
-
|
|
258
|
-
const response = await fetchLike(
|
|
259
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
260
|
-
{ method: "POST", body: form },
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
264
|
-
|
|
265
|
-
expect(echo.fields["title"]).toBe("My Upload");
|
|
266
|
-
expect(echo.fields["note"]).toBe("optional note");
|
|
267
|
-
|
|
268
|
-
const file = echo.fields["file"] as {
|
|
269
|
-
filename: string;
|
|
270
|
-
content: string;
|
|
271
|
-
};
|
|
272
|
-
expect(file.filename).toBe("index.html");
|
|
273
|
-
expect(file.content).toBe("<html><body>hi</body></html>");
|
|
274
|
-
} finally {
|
|
275
|
-
await fetchLike.close();
|
|
276
|
-
}
|
|
277
|
-
});
|
|
278
|
-
|
|
279
|
-
test("empty FormData sends a structurally valid body", async () => {
|
|
280
|
-
const fetchLike = createFetch();
|
|
281
|
-
|
|
282
|
-
try {
|
|
283
|
-
const response = await fetchLike(
|
|
284
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
285
|
-
{ method: "POST", body: new FormData() },
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
expect(response.status).toBe(200);
|
|
289
|
-
|
|
290
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
291
|
-
expect(echo.fields).toEqual({});
|
|
292
|
-
expect(echo.contentLength).toBeGreaterThan(0);
|
|
293
|
-
} finally {
|
|
294
|
-
await fetchLike.close();
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
test("Content-Length is accurate for a large Blob field", async () => {
|
|
299
|
-
const fetchLike = createFetch();
|
|
300
|
-
|
|
301
|
-
try {
|
|
302
|
-
const large = new Uint8Array(64 * 1024).fill(0x41);
|
|
303
|
-
const form = new FormData();
|
|
304
|
-
form.append(
|
|
305
|
-
"payload",
|
|
306
|
-
new Blob([large], { type: "application/octet-stream" }),
|
|
307
|
-
"large.bin",
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
const response = await fetchLike(
|
|
311
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
312
|
-
{ method: "POST", body: form },
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
expect(response.status).toBe(200);
|
|
316
|
-
|
|
317
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
318
|
-
const file = echo.fields["payload"] as { size: number };
|
|
319
|
-
|
|
320
|
-
expect(file.size).toBe(64 * 1024);
|
|
321
|
-
expect(echo.contentLength).toBeGreaterThan(64 * 1024);
|
|
322
|
-
} finally {
|
|
323
|
-
await fetchLike.close();
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
test("Request object with FormData body is forwarded correctly", async () => {
|
|
328
|
-
const fetchLike = createFetch();
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
const form = new FormData();
|
|
332
|
-
form.append("from", "request-object");
|
|
333
|
-
|
|
334
|
-
const request = new Request(
|
|
335
|
-
`${testServer.baseUrl}/multipart-echo`,
|
|
336
|
-
{ method: "POST", body: form },
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
const response = await fetchLike(request);
|
|
340
|
-
expect(response.status).toBe(200);
|
|
341
|
-
|
|
342
|
-
const echo: MultipartEchoResponse = await response.json();
|
|
343
|
-
expect(echo.fields["from"]).toBe("request-object");
|
|
344
|
-
} finally {
|
|
345
|
-
await fetchLike.close();
|
|
346
|
-
}
|
|
347
|
-
});
|
|
348
|
-
});
|
package/tests/test-utils.ts
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
import { gzipSync } from "node:zlib";
|
|
2
|
-
|
|
3
|
-
export interface TestServer {
|
|
4
|
-
server: ReturnType<typeof Bun.serve>;
|
|
5
|
-
baseUrl: string;
|
|
6
|
-
stop(): Promise<void>;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const encoder = new TextEncoder();
|
|
10
|
-
const decoder = new TextDecoder();
|
|
11
|
-
|
|
12
|
-
export function sleep(ms: number): Promise<void> {
|
|
13
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function headersToObject(headers: Headers): Record<string, string> {
|
|
17
|
-
const out: Record<string, string> = {};
|
|
18
|
-
for (const [key, value] of headers.entries()) {
|
|
19
|
-
out[key] = value;
|
|
20
|
-
}
|
|
21
|
-
return out;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function json(value: unknown, init?: ResponseInit): Response {
|
|
25
|
-
return Response.json(value, init);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function textResponse(
|
|
29
|
-
body: string,
|
|
30
|
-
init?: ResponseInit & { headers?: Record<string, string> },
|
|
31
|
-
): Response {
|
|
32
|
-
return new Response(body, {
|
|
33
|
-
...init,
|
|
34
|
-
headers: {
|
|
35
|
-
"content-type": "text/plain; charset=utf-8",
|
|
36
|
-
...(init?.headers ?? {}),
|
|
37
|
-
},
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function headAwareText(
|
|
42
|
-
request: Request,
|
|
43
|
-
body: string,
|
|
44
|
-
init?: ResponseInit & { headers?: Record<string, string> },
|
|
45
|
-
): Response {
|
|
46
|
-
if (request.method === "HEAD") {
|
|
47
|
-
return new Response(null, {
|
|
48
|
-
...init,
|
|
49
|
-
headers: {
|
|
50
|
-
"content-type": "text/plain; charset=utf-8",
|
|
51
|
-
...(init?.headers ?? {}),
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return textResponse(body, init);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function headAwareJson(
|
|
60
|
-
request: Request,
|
|
61
|
-
body: unknown,
|
|
62
|
-
init?: ResponseInit & { headers?: Record<string, string> },
|
|
63
|
-
): Response {
|
|
64
|
-
if (request.method === "HEAD") {
|
|
65
|
-
return new Response(null, {
|
|
66
|
-
...init,
|
|
67
|
-
headers: {
|
|
68
|
-
"content-type": "application/json",
|
|
69
|
-
...(init?.headers ?? {}),
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return json(body, {
|
|
75
|
-
...init,
|
|
76
|
-
headers: {
|
|
77
|
-
"content-type": "application/json",
|
|
78
|
-
...(init?.headers ?? {}),
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function createTestServer(): TestServer {
|
|
84
|
-
const server = Bun.serve({
|
|
85
|
-
hostname: "127.0.0.1",
|
|
86
|
-
port: 0,
|
|
87
|
-
async fetch(request: Request): Promise<Response> {
|
|
88
|
-
const url = new URL(request.url);
|
|
89
|
-
const headers = headersToObject(request.headers);
|
|
90
|
-
|
|
91
|
-
switch (url.pathname) {
|
|
92
|
-
case "/text": {
|
|
93
|
-
return headAwareText(request, "Hello, World!");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
case "/json": {
|
|
97
|
-
return headAwareJson(request, { message: "Hello, JSON!" });
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
case "/echo": {
|
|
101
|
-
if (request.method === "HEAD") {
|
|
102
|
-
return new Response(null, {
|
|
103
|
-
status: 200,
|
|
104
|
-
headers: {
|
|
105
|
-
"content-type": "application/json",
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const bodyBytes = new Uint8Array(
|
|
111
|
-
await request.arrayBuffer(),
|
|
112
|
-
);
|
|
113
|
-
const bodyText = decoder.decode(bodyBytes);
|
|
114
|
-
|
|
115
|
-
return json(
|
|
116
|
-
{
|
|
117
|
-
method: request.method,
|
|
118
|
-
url: `${url.pathname}${url.search}`,
|
|
119
|
-
headers,
|
|
120
|
-
bodyText,
|
|
121
|
-
bodyLength: bodyBytes.byteLength,
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
status: 200,
|
|
125
|
-
headers: {
|
|
126
|
-
"content-type": "application/json",
|
|
127
|
-
},
|
|
128
|
-
},
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
case "/slow": {
|
|
133
|
-
await sleep(200);
|
|
134
|
-
return textResponse("Finally!");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
case "/slow-body": {
|
|
138
|
-
let sent = 0;
|
|
139
|
-
|
|
140
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
141
|
-
async pull(controller) {
|
|
142
|
-
if (sent === 0) {
|
|
143
|
-
controller.enqueue(encoder.encode("part-1 "));
|
|
144
|
-
sent = 1;
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (sent === 1) {
|
|
149
|
-
await sleep(200);
|
|
150
|
-
controller.enqueue(encoder.encode("part-2"));
|
|
151
|
-
controller.close();
|
|
152
|
-
sent = 2;
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return new Response(stream, {
|
|
158
|
-
status: 200,
|
|
159
|
-
headers: {
|
|
160
|
-
"content-type": "text/plain; charset=utf-8",
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
case "/chunked": {
|
|
166
|
-
let index = 0;
|
|
167
|
-
const chunks = ["chunk1", "chunk2", "chunk3"];
|
|
168
|
-
|
|
169
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
170
|
-
pull(controller) {
|
|
171
|
-
if (index >= chunks.length) {
|
|
172
|
-
controller.close();
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
controller.enqueue(encoder.encode(chunks[index]));
|
|
177
|
-
index += 1;
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
return new Response(stream, {
|
|
182
|
-
status: 200,
|
|
183
|
-
headers: {
|
|
184
|
-
"content-type": "text/plain; charset=utf-8",
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
case "/gzip": {
|
|
190
|
-
const payload = gzipSync("This is compressed content!");
|
|
191
|
-
|
|
192
|
-
return new Response(payload, {
|
|
193
|
-
status: 200,
|
|
194
|
-
headers: {
|
|
195
|
-
"content-type": "text/plain; charset=utf-8",
|
|
196
|
-
"content-encoding": "gzip",
|
|
197
|
-
"content-length": String(payload.byteLength),
|
|
198
|
-
},
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
case "/bad-gzip": {
|
|
203
|
-
const payload = encoder.encode("not actually gzip");
|
|
204
|
-
|
|
205
|
-
return new Response(payload, {
|
|
206
|
-
status: 200,
|
|
207
|
-
headers: {
|
|
208
|
-
"content-type": "text/plain; charset=utf-8",
|
|
209
|
-
"content-encoding": "gzip",
|
|
210
|
-
"content-length": String(payload.byteLength),
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
case "/large": {
|
|
216
|
-
const body = "x".repeat(1024);
|
|
217
|
-
|
|
218
|
-
return textResponse(body, {
|
|
219
|
-
headers: {
|
|
220
|
-
"content-length": String(
|
|
221
|
-
encoder.encode(body).byteLength,
|
|
222
|
-
),
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
case "/large-stream": {
|
|
228
|
-
let remaining = 32;
|
|
229
|
-
|
|
230
|
-
const stream = new ReadableStream<Uint8Array>({
|
|
231
|
-
pull(controller) {
|
|
232
|
-
if (remaining === 0) {
|
|
233
|
-
controller.close();
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
controller.enqueue(encoder.encode("x".repeat(64)));
|
|
238
|
-
remaining -= 1;
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
return new Response(stream, {
|
|
243
|
-
status: 200,
|
|
244
|
-
headers: {
|
|
245
|
-
"content-type": "text/plain; charset=utf-8",
|
|
246
|
-
},
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
case "/huge-header": {
|
|
251
|
-
return new Response("ok", {
|
|
252
|
-
status: 200,
|
|
253
|
-
headers: {
|
|
254
|
-
"content-type": "text/plain; charset=utf-8",
|
|
255
|
-
"x-huge": "a".repeat(4096),
|
|
256
|
-
},
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
case "/redirect": {
|
|
261
|
-
return textResponse("Redirecting to /redirected-target", {
|
|
262
|
-
status: 302,
|
|
263
|
-
headers: {
|
|
264
|
-
location: "/redirected-target",
|
|
265
|
-
},
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
case "/redirected-target": {
|
|
270
|
-
return textResponse("You followed the redirect");
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
case "/multipart-echo": {
|
|
274
|
-
let fields: Record<string, unknown>;
|
|
275
|
-
|
|
276
|
-
try {
|
|
277
|
-
const formData = await request.formData();
|
|
278
|
-
fields = {};
|
|
279
|
-
|
|
280
|
-
for (const [key, value] of formData.entries()) {
|
|
281
|
-
const v = value as unknown;
|
|
282
|
-
|
|
283
|
-
if (v instanceof Blob) {
|
|
284
|
-
fields[key] = {
|
|
285
|
-
type: "file",
|
|
286
|
-
filename:
|
|
287
|
-
"name" in v
|
|
288
|
-
? String((v as any).name)
|
|
289
|
-
: "blob",
|
|
290
|
-
mimeType: (v as Blob).type,
|
|
291
|
-
size: (v as Blob).size,
|
|
292
|
-
content: await (v as Blob).text(),
|
|
293
|
-
};
|
|
294
|
-
} else {
|
|
295
|
-
const existing = fields[key];
|
|
296
|
-
fields[key] =
|
|
297
|
-
existing !== undefined
|
|
298
|
-
? [
|
|
299
|
-
...(Array.isArray(existing)
|
|
300
|
-
? existing
|
|
301
|
-
: [existing]),
|
|
302
|
-
value,
|
|
303
|
-
]
|
|
304
|
-
: value;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
} catch (err) {
|
|
308
|
-
return json({ error: String(err) }, { status: 400 });
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return json({
|
|
312
|
-
method: request.method,
|
|
313
|
-
contentType: request.headers.get("content-type"),
|
|
314
|
-
contentLength: request.headers.get("content-length")
|
|
315
|
-
? Number(request.headers.get("content-length"))
|
|
316
|
-
: null,
|
|
317
|
-
fields,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
default: {
|
|
322
|
-
return textResponse("Not Found", { status: 404 });
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
},
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
return {
|
|
329
|
-
server,
|
|
330
|
-
baseUrl: `http://${server.hostname}:${server.port}`,
|
|
331
|
-
async stop() {
|
|
332
|
-
await server.stop(true);
|
|
333
|
-
},
|
|
334
|
-
};
|
|
335
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"isolatedDeclarations": false
|
|
5
|
-
},
|
|
6
|
-
"include": ["src/**/*.ts"],
|
|
7
|
-
"exclude": [
|
|
8
|
-
"tests",
|
|
9
|
-
"examples",
|
|
10
|
-
"benchmarks",
|
|
11
|
-
"**/*.test.ts",
|
|
12
|
-
"**/*.test-utils.ts",
|
|
13
|
-
"**/__fixtures__/**"
|
|
14
|
-
]
|
|
15
|
-
}
|