@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.
- package/_internal/decode-stream-error.cjs +18 -0
- package/_internal/decode-stream-error.d.cts +11 -0
- 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 +5 -6
- package/{src/_internal → _internal}/guards.d.cts +2 -0
- package/{src/_internal → _internal}/guards.d.ts +2 -0
- package/_internal/guards.js +5 -7
- package/{src/_internal → _internal}/net.d.cts +1 -2
- package/{src/_internal → _internal}/net.d.ts +1 -2
- 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/agent-pool.cjs +23 -5
- package/agent-pool.d.cts +2 -0
- package/agent-pool.d.ts +2 -0
- package/agent-pool.js +23 -5
- package/agent.cjs +17 -14
- package/agent.js +17 -14
- package/body.cjs +10 -59
- package/body.d.cts +12 -0
- package/body.d.ts +12 -0
- package/body.js +11 -60
- package/dialers/proxy.cjs +7 -0
- package/{src/dialers → dialers}/proxy.d.cts +11 -3
- package/{src/dialers → dialers}/proxy.d.ts +11 -3
- package/dialers/proxy.js +7 -0
- package/dialers/tcp.cjs +22 -0
- package/{src/dialers → dialers}/tcp.d.cts +23 -2
- package/{src/dialers → dialers}/tcp.d.ts +23 -2
- package/dialers/tcp.js +22 -0
- package/encoding.cjs +32 -13
- package/encoding.d.cts +35 -0
- package/encoding.d.ts +35 -0
- package/encoding.js +32 -13
- package/fetch.cjs +279 -43
- package/fetch.d.cts +58 -0
- package/fetch.d.ts +58 -0
- package/fetch.js +278 -43
- package/http-client.cjs +47 -5
- package/http-client.d.cts +39 -0
- package/http-client.d.ts +39 -0
- package/http-client.js +47 -5
- package/index.cjs +7 -3
- package/index.d.cts +14 -1
- package/index.d.ts +14 -1
- package/index.js +6 -4
- package/io/io.cjs +68 -4
- package/{src/io → io}/io.d.cts +1 -1
- package/{src/io → io}/io.d.ts +1 -1
- package/io/io.js +68 -4
- package/io/readers.cjs +14 -54
- package/io/readers.d.cts +69 -0
- package/io/readers.d.ts +69 -0
- package/io/readers.js +14 -54
- package/io/writers.cjs +10 -5
- package/{src/io → io}/writers.d.cts +1 -1
- package/{src/io → io}/writers.d.ts +1 -1
- package/io/writers.js +11 -6
- package/package.json +18 -2
- package/types/agent.d.cts +72 -0
- package/types/agent.d.ts +72 -0
- package/{src/types → types}/dialer.d.cts +3 -0
- package/{src/types → types}/dialer.d.ts +3 -0
- package/_internal/error-adapters.cjs +0 -146
- package/_internal/error-adapters.js +0 -142
- package/src/_internal/error-adapters.d.cts +0 -22
- package/src/_internal/error-adapters.d.ts +0 -22
- package/src/agent-pool.d.cts +0 -2
- package/src/agent-pool.d.ts +0 -2
- package/src/body.d.cts +0 -23
- package/src/body.d.ts +0 -23
- package/src/encoding.d.cts +0 -24
- package/src/encoding.d.ts +0 -24
- package/src/fetch.d.cts +0 -36
- package/src/fetch.d.ts +0 -36
- package/src/http-client.d.cts +0 -23
- package/src/http-client.d.ts +0 -23
- package/src/index.d.cts +0 -7
- package/src/index.d.ts +0 -7
- package/src/io/readers.d.cts +0 -199
- package/src/io/readers.d.ts +0 -199
- package/src/types/agent.d.cts +0 -128
- package/src/types/agent.d.ts +0 -128
- package/tests/test-utils.d.cts +0 -8
- package/tests/test-utils.d.ts +0 -8
- /package/{src/_internal → _internal}/consts.d.cts +0 -0
- /package/{src/_internal → _internal}/consts.d.ts +0 -0
- /package/{src/_internal → _internal}/promises.d.cts +0 -0
- /package/{src/_internal → _internal}/promises.d.ts +0 -0
- /package/{src/_internal → _internal}/streams.d.cts +0 -0
- /package/{src/_internal → _internal}/streams.d.ts +0 -0
- /package/{src/agent.d.cts → agent.d.cts} +0 -0
- /package/{src/agent.d.ts → agent.d.ts} +0 -0
- /package/{src/dialers → dialers}/index.d.cts +0 -0
- /package/{src/dialers → dialers}/index.d.ts +0 -0
- /package/{src/errors.d.cts → errors.d.cts} +0 -0
- /package/{src/errors.d.ts → errors.d.ts} +0 -0
- /package/{src/io → io}/_utils.d.cts +0 -0
- /package/{src/io → io}/_utils.d.ts +0 -0
- /package/{src/io → io}/buf-writer.d.cts +0 -0
- /package/{src/io → io}/buf-writer.d.ts +0 -0
- /package/{src/types → types}/index.d.cts +0 -0
- /package/{src/types → types}/index.d.ts +0 -0
package/encoding.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DecodeStreamError } from "./_internal/decode-stream-error.js";
|
|
1
2
|
import { nodeReadableToWeb } from "@fuman/node";
|
|
2
3
|
import { Readable } from "node:stream";
|
|
3
4
|
//#region src/encoding.ts
|
|
@@ -17,10 +18,11 @@ function encodeStream(stream, contentEncoding) {
|
|
|
17
18
|
return applyTransforms(stream, contentEncoding, createEncoders);
|
|
18
19
|
}
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
* resulting streams should be piped together to decode the content.
|
|
21
|
+
* Creates the decoder pipeline for a Content-Encoding or transfer-coding list.
|
|
22
22
|
*
|
|
23
|
-
* @
|
|
23
|
+
* @remarks
|
|
24
|
+
* Decoding is applied in reverse order of encoding, as required by HTTP semantics.
|
|
25
|
+
* The special value `identity` is ignored.
|
|
24
26
|
*/
|
|
25
27
|
function createDecoders(contentEncoding) {
|
|
26
28
|
const decoders = [];
|
|
@@ -35,13 +37,10 @@ function createDecoders(contentEncoding) {
|
|
|
35
37
|
return decoders.reverse();
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
|
-
*
|
|
39
|
-
* transfer-coding list).
|
|
40
|
+
* Creates the encoder pipeline for a Content-Encoding or transfer-coding list.
|
|
40
41
|
*
|
|
41
|
-
*
|
|
42
|
-
* declared order.
|
|
43
|
-
*
|
|
44
|
-
* @see {@link https://datatracker.ietf.org/doc/html/rfc9110#section-8.4.1}
|
|
42
|
+
* @remarks
|
|
43
|
+
* Encoders are returned in the declared order. The special value `identity` is ignored.
|
|
45
44
|
*/
|
|
46
45
|
function createEncoders(contentEncoding) {
|
|
47
46
|
const encoders = [];
|
|
@@ -61,15 +60,35 @@ function commaSplit(header) {
|
|
|
61
60
|
function normalizeEncoding(encoding) {
|
|
62
61
|
return encoding.trim().toLowerCase();
|
|
63
62
|
}
|
|
63
|
+
function tagDecodeErrors(native) {
|
|
64
|
+
const reader = native.readable.getReader();
|
|
65
|
+
const tagged = new ReadableStream({
|
|
66
|
+
pull(controller) {
|
|
67
|
+
return reader.read().then(({ done, value }) => {
|
|
68
|
+
if (done) controller.close();
|
|
69
|
+
else if (value) controller.enqueue(value);
|
|
70
|
+
}, (err) => {
|
|
71
|
+
controller.error(new DecodeStreamError(err));
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
cancel(reason) {
|
|
75
|
+
return reader.cancel(reason);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
writable: native.writable,
|
|
80
|
+
readable: tagged
|
|
81
|
+
};
|
|
82
|
+
}
|
|
64
83
|
function createDecoder(normalizedEncoding) {
|
|
65
84
|
switch (normalizedEncoding) {
|
|
66
85
|
case "gzip":
|
|
67
|
-
case "x-gzip": return new DecompressionStream("gzip");
|
|
86
|
+
case "x-gzip": return tagDecodeErrors(new DecompressionStream("gzip"));
|
|
68
87
|
case "deflate":
|
|
69
|
-
case "x-deflate": return new DecompressionStream("deflate");
|
|
88
|
+
case "x-deflate": return tagDecodeErrors(new DecompressionStream("deflate"));
|
|
70
89
|
case "zstd":
|
|
71
|
-
case "x-zstd": return new DecompressionStream("zstd");
|
|
72
|
-
case "br": return new DecompressionStream("brotli");
|
|
90
|
+
case "x-zstd": return tagDecodeErrors(new DecompressionStream("zstd"));
|
|
91
|
+
case "br": return tagDecodeErrors(new DecompressionStream("brotli"));
|
|
73
92
|
case "identity": return new TransformStream();
|
|
74
93
|
default: throw new TypeError(`Unsupported content-encoding: "${normalizedEncoding}"`);
|
|
75
94
|
}
|
package/fetch.cjs
CHANGED
|
@@ -1,53 +1,82 @@
|
|
|
1
|
+
require("./_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_error_mapping = require("./_internal/error-mapping.cjs");
|
|
3
|
+
const require_symbols = require("./_internal/symbols.cjs");
|
|
4
|
+
const require_guards = require("./_internal/guards.cjs");
|
|
5
|
+
const require_body = require("./body.cjs");
|
|
6
|
+
const require_proxy = require("./dialers/proxy.cjs");
|
|
1
7
|
const require_tcp = require("./dialers/tcp.cjs");
|
|
2
|
-
const require_error_adapters = require("./_internal/error-adapters.cjs");
|
|
3
8
|
const require_http_client = require("./http-client.cjs");
|
|
9
|
+
let _npy_proxy_kit = require("@npy/proxy-kit");
|
|
4
10
|
//#region src/fetch.ts
|
|
11
|
+
var MAX_REDIRECTS = 20;
|
|
12
|
+
var REDIRECT_STATUSES = new Set([
|
|
13
|
+
301,
|
|
14
|
+
302,
|
|
15
|
+
303,
|
|
16
|
+
307,
|
|
17
|
+
308
|
|
18
|
+
]);
|
|
19
|
+
var SENSITIVE_REDIRECT_HEADERS = [
|
|
20
|
+
"authorization",
|
|
21
|
+
"cookie",
|
|
22
|
+
"proxy-authorization"
|
|
23
|
+
];
|
|
24
|
+
var BODY_HEADERS = [
|
|
25
|
+
"content-encoding",
|
|
26
|
+
"content-language",
|
|
27
|
+
"content-length",
|
|
28
|
+
"content-location",
|
|
29
|
+
"content-type",
|
|
30
|
+
"transfer-encoding"
|
|
31
|
+
];
|
|
5
32
|
function createDefaultHttpClient(options = {}) {
|
|
6
33
|
return new require_http_client.HttpClient({
|
|
7
34
|
...options,
|
|
8
35
|
dialer: options.dialer ?? new require_tcp.AutoDialer()
|
|
9
36
|
});
|
|
10
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Normalizes any supported header input into a {@link Headers} instance.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* If the input is already a {@link Headers} object, the same instance is returned.
|
|
43
|
+
* Tuple arrays and plain records are copied into a new {@link Headers}.
|
|
44
|
+
*/
|
|
11
45
|
function normalizeHeaders(headers) {
|
|
12
46
|
if (headers instanceof Headers) return headers;
|
|
13
|
-
const
|
|
47
|
+
const result = new Headers();
|
|
48
|
+
if (!headers) return result;
|
|
14
49
|
if (Array.isArray(headers)) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
return normalized;
|
|
50
|
+
for (const [key, value] of headers) result.append(key, value);
|
|
51
|
+
return result;
|
|
19
52
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
});
|
|
24
|
-
else if (value !== void 0) normalized.append(key, value);
|
|
25
|
-
});
|
|
26
|
-
return normalized;
|
|
53
|
+
for (const [key, value] of Object.entries(headers)) if (Array.isArray(value)) for (const entry of value) result.append(key, entry);
|
|
54
|
+
else if (value !== void 0) result.append(key, value);
|
|
55
|
+
return result;
|
|
27
56
|
}
|
|
28
57
|
function resolveUrl(input) {
|
|
29
58
|
if (input instanceof URL) return input;
|
|
30
|
-
|
|
31
|
-
return new URL(String(input));
|
|
59
|
+
return new URL(input instanceof Request ? input.url : String(input));
|
|
32
60
|
}
|
|
33
61
|
function resolveMethod(input, init) {
|
|
34
|
-
|
|
35
|
-
if (input instanceof Request) return input.method.toUpperCase();
|
|
36
|
-
return "GET";
|
|
62
|
+
return (init.method ?? (input instanceof Request ? input.method : void 0))?.toUpperCase().trim() ?? "GET";
|
|
37
63
|
}
|
|
38
64
|
function resolveHeaders(input, init) {
|
|
39
|
-
|
|
40
|
-
if (input instanceof Request) return normalizeHeaders(input.headers);
|
|
41
|
-
return new Headers();
|
|
65
|
+
return normalizeHeaders(init.headers !== void 0 ? init.headers : input instanceof Request ? input.headers : void 0);
|
|
42
66
|
}
|
|
43
67
|
function resolveSignal(input, init) {
|
|
44
68
|
return init.signal ?? (input instanceof Request ? input.signal : void 0);
|
|
45
69
|
}
|
|
46
70
|
function resolveBody(input, init) {
|
|
47
71
|
if (init.body !== void 0) return init.body;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
return input instanceof Request ? require_body.fromRequestBody(input) : void 0;
|
|
73
|
+
}
|
|
74
|
+
function resolveRedirectMode(input, init) {
|
|
75
|
+
return init.redirect ?? (input instanceof Request ? input.redirect : "follow");
|
|
76
|
+
}
|
|
77
|
+
function getBodyReplaySource(input, init, method, body, redirect) {
|
|
78
|
+
if (redirect !== "follow" || init.body !== void 0 || body == null || method === "GET" || method === "HEAD" || !(input instanceof Request)) return;
|
|
79
|
+
return input.clone();
|
|
51
80
|
}
|
|
52
81
|
function assertValidFetchUrl(url) {
|
|
53
82
|
if (url.username || url.password) throw new TypeError("Request URL must not include embedded credentials");
|
|
@@ -57,36 +86,236 @@ function assertValidFetchBody(method, body) {
|
|
|
57
86
|
if (body == null) return;
|
|
58
87
|
if (method === "GET" || method === "HEAD") throw new TypeError(`Request with ${method} method cannot have a body`);
|
|
59
88
|
}
|
|
60
|
-
|
|
89
|
+
function lookupEnvProxy(...names) {
|
|
90
|
+
for (const name of names) {
|
|
91
|
+
const normalized = (process.env[name] ?? process.env[name.toLowerCase()])?.trim();
|
|
92
|
+
if (normalized) return normalized;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function resolveProxyFromEnv(url) {
|
|
96
|
+
const socksProxy = lookupEnvProxy("SOCKS5_PROXY", "SOCKS_PROXY");
|
|
97
|
+
if (socksProxy) return socksProxy;
|
|
98
|
+
if (url.protocol === "https:") return lookupEnvProxy("HTTPS_PROXY", "HTTP_PROXY");
|
|
99
|
+
return lookupEnvProxy("HTTP_PROXY");
|
|
100
|
+
}
|
|
101
|
+
function normalizeProxyConfig(proxy) {
|
|
102
|
+
const parsed = typeof proxy === "string" ? (0, _npy_proxy_kit.parse)(proxy, { strict: true }) : proxy;
|
|
103
|
+
if (parsed == null) throw new TypeError(`Invalid proxy string: ${String(proxy)}`);
|
|
104
|
+
const key = (0, _npy_proxy_kit.stringify)(parsed, {
|
|
105
|
+
strict: true,
|
|
106
|
+
format: "user:pass@ip:port"
|
|
107
|
+
});
|
|
108
|
+
if (!key) throw new TypeError("Failed to normalize proxy configuration");
|
|
109
|
+
return {
|
|
110
|
+
key,
|
|
111
|
+
proxy: parsed
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function resolveNormalizedProxy(url, proxy) {
|
|
115
|
+
if (proxy === null) return null;
|
|
116
|
+
if (proxy !== void 0) return normalizeProxyConfig(proxy);
|
|
117
|
+
const envProxy = resolveProxyFromEnv(url);
|
|
118
|
+
return envProxy ? normalizeProxyConfig(envProxy) : null;
|
|
119
|
+
}
|
|
120
|
+
function annotateResponse(response, url, redirected) {
|
|
121
|
+
try {
|
|
122
|
+
if (redirected) Object.defineProperties(response, {
|
|
123
|
+
redirected: {
|
|
124
|
+
configurable: true,
|
|
125
|
+
enumerable: true,
|
|
126
|
+
value: true,
|
|
127
|
+
writable: false
|
|
128
|
+
},
|
|
129
|
+
url: {
|
|
130
|
+
configurable: true,
|
|
131
|
+
enumerable: true,
|
|
132
|
+
value: url,
|
|
133
|
+
writable: false
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
else Object.defineProperty(response, "url", {
|
|
137
|
+
configurable: true,
|
|
138
|
+
enumerable: true,
|
|
139
|
+
value: url,
|
|
140
|
+
writable: false
|
|
141
|
+
});
|
|
142
|
+
} catch {}
|
|
143
|
+
return response;
|
|
144
|
+
}
|
|
145
|
+
async function discardResponse(response) {
|
|
146
|
+
try {
|
|
147
|
+
await response.body?.cancel();
|
|
148
|
+
} catch {}
|
|
149
|
+
}
|
|
150
|
+
function isRedirectResponse(response) {
|
|
151
|
+
return REDIRECT_STATUSES.has(response.status) && response.headers.get("location") != null;
|
|
152
|
+
}
|
|
153
|
+
function isReplayableBody(body) {
|
|
154
|
+
if (body == null || typeof body === "string" || body instanceof Uint8Array || require_guards.isBlob(body) || require_guards.isFormData(body) || require_guards.isURLSearchParameters(body)) return true;
|
|
155
|
+
if (require_guards.isReadableStream(body) || require_guards.isReadable(body) || require_guards.isMultipartFormDataStream(body) || require_guards.isIterable(body)) return false;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
async function resolveReplayableBody(current) {
|
|
159
|
+
if (isReplayableBody(current.body)) return current.body;
|
|
160
|
+
if (current.replayBodyRequest) {
|
|
161
|
+
const buffer = await current.replayBodyRequest.arrayBuffer();
|
|
162
|
+
return buffer.byteLength > 0 ? new Uint8Array(buffer) : null;
|
|
163
|
+
}
|
|
164
|
+
throw new TypeError("Cannot follow redirect with a non-replayable request body");
|
|
165
|
+
}
|
|
166
|
+
function shouldDropBodyOnRedirect(status, method) {
|
|
167
|
+
if (status === 303) return method !== "HEAD";
|
|
168
|
+
return (status === 301 || status === 302) && method === "POST";
|
|
169
|
+
}
|
|
170
|
+
function resolveRedirectMethod(status, method) {
|
|
171
|
+
if (status === 303 && method !== "HEAD" || (status === 301 || status === 302) && method === "POST") return "GET";
|
|
172
|
+
return method;
|
|
173
|
+
}
|
|
174
|
+
function createRedirectHeaders(current, nextUrl, dropBody) {
|
|
175
|
+
const headers = new Headers(current.headers);
|
|
176
|
+
const isCrossOrigin = current.url.origin !== nextUrl.origin;
|
|
177
|
+
const isDowngrade = current.url.protocol === "https:" && nextUrl.protocol === "http:";
|
|
178
|
+
headers.delete("host");
|
|
179
|
+
if (isCrossOrigin) for (const name of SENSITIVE_REDIRECT_HEADERS) headers.delete(name);
|
|
180
|
+
if (dropBody) for (const name of BODY_HEADERS) headers.delete(name);
|
|
181
|
+
if (!headers.has("referer") && !isDowngrade) headers.set("referer", current.url.toString());
|
|
182
|
+
return headers;
|
|
183
|
+
}
|
|
184
|
+
async function buildRedirectRequest(current, response) {
|
|
185
|
+
const location = response.headers.get("location");
|
|
186
|
+
if (!location) return current;
|
|
187
|
+
const nextUrl = new URL(location, current.url);
|
|
188
|
+
const nextMethod = resolveRedirectMethod(response.status, current.method);
|
|
189
|
+
const dropBody = shouldDropBodyOnRedirect(response.status, current.method);
|
|
190
|
+
const nextHeaders = createRedirectHeaders(current, nextUrl, dropBody);
|
|
191
|
+
const nextBody = dropBody ? void 0 : await resolveReplayableBody(current);
|
|
192
|
+
return {
|
|
193
|
+
...current,
|
|
194
|
+
url: nextUrl,
|
|
195
|
+
method: nextMethod,
|
|
196
|
+
headers: nextHeaders,
|
|
197
|
+
body: nextBody,
|
|
198
|
+
replayBodyRequest: void 0
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
async function sendPreparedRequest(prepared, getProxyClient) {
|
|
202
|
+
assertValidFetchUrl(prepared.url);
|
|
203
|
+
assertValidFetchBody(prepared.method, prepared.body);
|
|
204
|
+
const normalizedProxy = resolveNormalizedProxy(prepared.url, prepared.proxy);
|
|
205
|
+
const client = normalizedProxy ? getProxyClient(prepared.baseClient, normalizedProxy) : prepared.baseClient;
|
|
206
|
+
try {
|
|
207
|
+
const sendOptions = Object.defineProperty({
|
|
208
|
+
url: prepared.url,
|
|
209
|
+
method: prepared.method,
|
|
210
|
+
headers: prepared.headers,
|
|
211
|
+
body: prepared.body ?? null,
|
|
212
|
+
signal: prepared.signal
|
|
213
|
+
}, require_symbols.bodyErrorMapperSymbol, {
|
|
214
|
+
configurable: false,
|
|
215
|
+
enumerable: false,
|
|
216
|
+
writable: false,
|
|
217
|
+
value: (error) => require_error_mapping.toWebBodyReadError(error, prepared.signal)
|
|
218
|
+
});
|
|
219
|
+
return await client.send(sendOptions);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
throw require_error_mapping.toWebFetchError(error, prepared.signal);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function prepareRequest(input, init, defaultHttpClient) {
|
|
61
225
|
const url = resolveUrl(input);
|
|
62
|
-
assertValidFetchUrl(url);
|
|
63
226
|
const method = resolveMethod(input, init);
|
|
64
227
|
const headers = resolveHeaders(input, init);
|
|
65
228
|
const body = resolveBody(input, init);
|
|
66
229
|
const signal = resolveSignal(input, init);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
230
|
+
const redirect = resolveRedirectMode(input, init);
|
|
231
|
+
return {
|
|
232
|
+
url,
|
|
233
|
+
method,
|
|
234
|
+
headers,
|
|
235
|
+
body,
|
|
236
|
+
signal,
|
|
237
|
+
redirect,
|
|
238
|
+
baseClient: init.client ?? defaultHttpClient,
|
|
239
|
+
proxy: init.proxy,
|
|
240
|
+
replayBodyRequest: getBodyReplaySource(input, init, method, body, redirect)
|
|
241
|
+
};
|
|
79
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Creates a fetch-compatible client backed by {@link HttpClient}.
|
|
245
|
+
*
|
|
246
|
+
* @remarks
|
|
247
|
+
* The returned function follows the standard fetch shape, but uses this library's
|
|
248
|
+
* connection pooling, proxy support and body/error mapping rules.
|
|
249
|
+
*
|
|
250
|
+
* When no client is provided, an internal {@link HttpClient} is created and owned
|
|
251
|
+
* by the returned fetch-like function. Calling {@link FetchLike.close} closes that
|
|
252
|
+
* internal client and any proxy-specific clients created on demand.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```ts
|
|
256
|
+
* const fetchLike = createFetch();
|
|
257
|
+
* const response = await fetchLike("https://httpbin.org/anything");
|
|
258
|
+
* const data = await response.json();
|
|
259
|
+
* await fetchLike.close();
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
80
262
|
function createFetch(client) {
|
|
81
263
|
const defaultHttpClient = client ?? createDefaultHttpClient();
|
|
264
|
+
const proxyClientsByBase = /* @__PURE__ */ new WeakMap();
|
|
265
|
+
const ownedProxyClients = /* @__PURE__ */ new Set();
|
|
266
|
+
let closePromise;
|
|
267
|
+
const getProxyClient = (baseClient, proxy) => {
|
|
268
|
+
let clients = proxyClientsByBase.get(baseClient);
|
|
269
|
+
if (!clients) {
|
|
270
|
+
clients = /* @__PURE__ */ new Map();
|
|
271
|
+
proxyClientsByBase.set(baseClient, clients);
|
|
272
|
+
}
|
|
273
|
+
const existing = clients.get(proxy.key);
|
|
274
|
+
if (existing) return existing;
|
|
275
|
+
const proxyClient = new require_http_client.HttpClient({
|
|
276
|
+
...baseClient.options,
|
|
277
|
+
dialer: new require_proxy.ProxyDialer(proxy.proxy)
|
|
278
|
+
});
|
|
279
|
+
clients.set(proxy.key, proxyClient);
|
|
280
|
+
ownedProxyClients.add(proxyClient);
|
|
281
|
+
return proxyClient;
|
|
282
|
+
};
|
|
82
283
|
const fetchLike = (async (input, init = {}) => {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
284
|
+
let prepared = prepareRequest(input, init, defaultHttpClient);
|
|
285
|
+
let redirected = false;
|
|
286
|
+
let redirects = 0;
|
|
287
|
+
for (;;) {
|
|
288
|
+
const response = await sendPreparedRequest(prepared, getProxyClient);
|
|
289
|
+
if (!isRedirectResponse(response) || prepared.redirect === "manual") return annotateResponse(response, prepared.url.toString(), redirected);
|
|
290
|
+
if (prepared.redirect === "error") {
|
|
291
|
+
await discardResponse(response);
|
|
292
|
+
throw new TypeError(`fetch failed: redirect mode is set to "error"`);
|
|
293
|
+
}
|
|
294
|
+
if (redirects >= MAX_REDIRECTS) {
|
|
295
|
+
await discardResponse(response);
|
|
296
|
+
throw new TypeError(`fetch failed: maximum redirect count exceeded`);
|
|
297
|
+
}
|
|
298
|
+
prepared = await buildRedirectRequest(prepared, response);
|
|
299
|
+
await discardResponse(response);
|
|
300
|
+
redirected = true;
|
|
301
|
+
redirects += 1;
|
|
302
|
+
}
|
|
87
303
|
});
|
|
88
304
|
const close = async () => {
|
|
89
|
-
if (
|
|
305
|
+
if (closePromise) return closePromise;
|
|
306
|
+
const promise = (async () => {
|
|
307
|
+
const proxyClients = Array.from(ownedProxyClients);
|
|
308
|
+
ownedProxyClients.clear();
|
|
309
|
+
const errors = (await Promise.allSettled([...proxyClients.map((c) => c.close()), ...client == null ? [defaultHttpClient.close()] : []])).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
310
|
+
if (errors.length === 1) throw errors[0];
|
|
311
|
+
if (errors.length > 1) throw new AggregateError(errors, "Failed to close one or more fetch clients");
|
|
312
|
+
})();
|
|
313
|
+
closePromise = promise;
|
|
314
|
+
try {
|
|
315
|
+
await promise;
|
|
316
|
+
} finally {
|
|
317
|
+
if (closePromise === promise) closePromise = void 0;
|
|
318
|
+
}
|
|
90
319
|
};
|
|
91
320
|
Object.defineProperties(fetchLike, {
|
|
92
321
|
client: {
|
|
@@ -110,6 +339,13 @@ function createFetch(client) {
|
|
|
110
339
|
});
|
|
111
340
|
return fetchLike;
|
|
112
341
|
}
|
|
342
|
+
/**
|
|
343
|
+
* Default fetch-compatible client created with {@link createFetch}.
|
|
344
|
+
*
|
|
345
|
+
* @remarks
|
|
346
|
+
* This singleton owns its internal {@link HttpClient}. Call {@link FetchLike.close}
|
|
347
|
+
* when you want to release pooled connections explicitly.
|
|
348
|
+
*/
|
|
113
349
|
var fetch = createFetch();
|
|
114
350
|
//#endregion
|
|
115
351
|
exports.createFetch = createFetch;
|
package/fetch.d.cts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ProxyInfo } from '@npy/proxy-kit';
|
|
2
|
+
import { BodyInit as FetchBodyInit } from './body';
|
|
3
|
+
import { HttpClientOptions, HttpClient } from './http-client';
|
|
4
|
+
export type FetchProxyInput = string | ProxyInfo | null;
|
|
5
|
+
export interface RequestInit extends Omit<globalThis.RequestInit, "body" | "headers"> {
|
|
6
|
+
body?: FetchBodyInit | null;
|
|
7
|
+
headers?: HeadersInit;
|
|
8
|
+
client?: HttpClient;
|
|
9
|
+
proxy?: FetchProxyInput;
|
|
10
|
+
}
|
|
11
|
+
export type FetchRequestInit = RequestInit;
|
|
12
|
+
export interface FetchOptions extends HttpClientOptions {
|
|
13
|
+
}
|
|
14
|
+
export interface FetchLike {
|
|
15
|
+
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
18
|
+
readonly client: HttpClient;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes any supported header input into a {@link Headers} instance.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* If the input is already a {@link Headers} object, the same instance is returned.
|
|
25
|
+
* Tuple arrays and plain records are copied into a new {@link Headers}.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeHeaders(headers?: HeadersInit): Headers;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a fetch-compatible client backed by {@link HttpClient}.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The returned function follows the standard fetch shape, but uses this library's
|
|
33
|
+
* connection pooling, proxy support and body/error mapping rules.
|
|
34
|
+
*
|
|
35
|
+
* When no client is provided, an internal {@link HttpClient} is created and owned
|
|
36
|
+
* by the returned fetch-like function. Calling {@link FetchLike.close} closes that
|
|
37
|
+
* internal client and any proxy-specific clients created on demand.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const fetchLike = createFetch();
|
|
42
|
+
* const response = await fetchLike("https://httpbin.org/anything");
|
|
43
|
+
* const data = await response.json();
|
|
44
|
+
* await fetchLike.close();
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function createFetch(client?: HttpClient): FetchLike;
|
|
48
|
+
export type { HttpClientOptions, ProxyInfo };
|
|
49
|
+
export { HttpClient };
|
|
50
|
+
/**
|
|
51
|
+
* Default fetch-compatible client created with {@link createFetch}.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* This singleton owns its internal {@link HttpClient}. Call {@link FetchLike.close}
|
|
55
|
+
* when you want to release pooled connections explicitly.
|
|
56
|
+
*/
|
|
57
|
+
export declare const fetch: FetchLike;
|
|
58
|
+
export default fetch;
|
package/fetch.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ProxyInfo } from '@npy/proxy-kit';
|
|
2
|
+
import { BodyInit as FetchBodyInit } from './body';
|
|
3
|
+
import { HttpClientOptions, HttpClient } from './http-client';
|
|
4
|
+
export type FetchProxyInput = string | ProxyInfo | null;
|
|
5
|
+
export interface RequestInit extends Omit<globalThis.RequestInit, "body" | "headers"> {
|
|
6
|
+
body?: FetchBodyInit | null;
|
|
7
|
+
headers?: HeadersInit;
|
|
8
|
+
client?: HttpClient;
|
|
9
|
+
proxy?: FetchProxyInput;
|
|
10
|
+
}
|
|
11
|
+
export type FetchRequestInit = RequestInit;
|
|
12
|
+
export interface FetchOptions extends HttpClientOptions {
|
|
13
|
+
}
|
|
14
|
+
export interface FetchLike {
|
|
15
|
+
(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
18
|
+
readonly client: HttpClient;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalizes any supported header input into a {@link Headers} instance.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* If the input is already a {@link Headers} object, the same instance is returned.
|
|
25
|
+
* Tuple arrays and plain records are copied into a new {@link Headers}.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeHeaders(headers?: HeadersInit): Headers;
|
|
28
|
+
/**
|
|
29
|
+
* Creates a fetch-compatible client backed by {@link HttpClient}.
|
|
30
|
+
*
|
|
31
|
+
* @remarks
|
|
32
|
+
* The returned function follows the standard fetch shape, but uses this library's
|
|
33
|
+
* connection pooling, proxy support and body/error mapping rules.
|
|
34
|
+
*
|
|
35
|
+
* When no client is provided, an internal {@link HttpClient} is created and owned
|
|
36
|
+
* by the returned fetch-like function. Calling {@link FetchLike.close} closes that
|
|
37
|
+
* internal client and any proxy-specific clients created on demand.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* const fetchLike = createFetch();
|
|
42
|
+
* const response = await fetchLike("https://httpbin.org/anything");
|
|
43
|
+
* const data = await response.json();
|
|
44
|
+
* await fetchLike.close();
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function createFetch(client?: HttpClient): FetchLike;
|
|
48
|
+
export type { HttpClientOptions, ProxyInfo };
|
|
49
|
+
export { HttpClient };
|
|
50
|
+
/**
|
|
51
|
+
* Default fetch-compatible client created with {@link createFetch}.
|
|
52
|
+
*
|
|
53
|
+
* @remarks
|
|
54
|
+
* This singleton owns its internal {@link HttpClient}. Call {@link FetchLike.close}
|
|
55
|
+
* when you want to release pooled connections explicitly.
|
|
56
|
+
*/
|
|
57
|
+
export declare const fetch: FetchLike;
|
|
58
|
+
export default fetch;
|