rekwest 5.3.0 → 5.4.0
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/README.md +245 -245
- package/dist/{defaults.js → config.js} +12 -3
- package/dist/index.js +3 -3
- package/dist/preflight.js +11 -5
- package/dist/utils.js +8 -2
- package/package.json +10 -9
- package/src/{defaults.mjs → config.mjs} +16 -2
- package/src/formdata.mjs +246 -244
- package/src/index.mjs +3 -3
- package/src/preflight.mjs +13 -9
- package/src/utils.mjs +6 -2
package/README.md
CHANGED
|
@@ -1,245 +1,245 @@
|
|
|
1
|
-
The robust request library that humanity deserves 🌐
|
|
2
|
-
---
|
|
3
|
-
This package provides highly likely functional and **easy-to-use** abstraction atop of
|
|
4
|
-
native [http(s).request](https://nodejs.org/api/https.html#httpsrequesturl-options-callback)
|
|
5
|
-
and [http2.request](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options).
|
|
6
|
-
|
|
7
|
-
## Abstract
|
|
8
|
-
|
|
9
|
-
* Fetch-alike
|
|
10
|
-
* Cool-beans 🫐 config options (with defaults)
|
|
11
|
-
* Automatic HTTP/2 support (ALPN negotiation)
|
|
12
|
-
* Automatic or opt-in body parse (with non-UTF-8 charset decoding)
|
|
13
|
-
* Automatic and simplistic `Cookies` treatment (with built-in **jar** & **ttl**)
|
|
14
|
-
* Automatic decompression (with opt-in body compression)
|
|
15
|
-
* Built-in streamable `FormData` interface
|
|
16
|
-
* Support redirects & retries with fine-grained tune-ups
|
|
17
|
-
* Support all legit request body types (include blobs & streams)
|
|
18
|
-
* Support both CJS and ESM module systems
|
|
19
|
-
* Fully promise-able and pipe-able
|
|
20
|
-
* Zero dependencies
|
|
21
|
-
|
|
22
|
-
## Prerequisites
|
|
23
|
-
|
|
24
|
-
* Node.js `>= 18.13.0`
|
|
25
|
-
|
|
26
|
-
## Installation
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
npm install rekwest --save
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### Usage
|
|
33
|
-
|
|
34
|
-
```javascript
|
|
35
|
-
import rekwest, { constants } from 'rekwest';
|
|
36
|
-
|
|
37
|
-
const {
|
|
38
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
39
|
-
HTTP2_HEADER_CONTENT_ENCODING,
|
|
40
|
-
HTTP2_METHOD_POST,
|
|
41
|
-
HTTP_STATUS_OK,
|
|
42
|
-
} = constants;
|
|
43
|
-
|
|
44
|
-
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
45
|
-
|
|
46
|
-
const res = await rekwest(url, {
|
|
47
|
-
body: { celestial: 'payload' },
|
|
48
|
-
headers: {
|
|
49
|
-
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
50
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
51
|
-
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
52
|
-
* is undue for
|
|
53
|
-
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
54
|
-
* and will be set automatically, with an option to override it here
|
|
55
|
-
*/
|
|
56
|
-
},
|
|
57
|
-
method: HTTP2_METHOD_POST,
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
61
|
-
console.info(res.headers);
|
|
62
|
-
console.log(res.body);
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
```javascript
|
|
68
|
-
import { Readable } from 'node:stream';
|
|
69
|
-
import rekwest, {
|
|
70
|
-
constants,
|
|
71
|
-
Blob,
|
|
72
|
-
File,
|
|
73
|
-
FormData,
|
|
74
|
-
} from 'rekwest';
|
|
75
|
-
|
|
76
|
-
const {
|
|
77
|
-
HTTP2_HEADER_AUTHORIZATION,
|
|
78
|
-
HTTP2_HEADER_CONTENT_ENCODING,
|
|
79
|
-
HTTP2_METHOD_POST,
|
|
80
|
-
HTTP_STATUS_OK,
|
|
81
|
-
} = constants;
|
|
82
|
-
|
|
83
|
-
const blob = new Blob(['bits']);
|
|
84
|
-
const file = new File(['bits'], 'file.dab');
|
|
85
|
-
const readable = Readable.from('bits');
|
|
86
|
-
|
|
87
|
-
const fd = new FormData({
|
|
88
|
-
aux: Date.now(), // either [[key, value]] or kv sequenceable
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
fd.append('celestial', 'payload');
|
|
92
|
-
fd.append('blob', blob, 'blob.dab');
|
|
93
|
-
fd.append('file', file);
|
|
94
|
-
fd.append('readable', readable, 'readable.dab');
|
|
95
|
-
|
|
96
|
-
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
97
|
-
|
|
98
|
-
const res = await rekwest(url, {
|
|
99
|
-
body: fd,
|
|
100
|
-
headers: {
|
|
101
|
-
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
102
|
-
[HTTP2_HEADER_CONTENT_ENCODING]: '
|
|
103
|
-
},
|
|
104
|
-
method: HTTP2_METHOD_POST,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
108
|
-
console.info(res.headers);
|
|
109
|
-
console.log(res.body);
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### API
|
|
113
|
-
|
|
114
|
-
#### `rekwest(url[, options])`
|
|
115
|
-
|
|
116
|
-
* `url` **{string | URL}** The URL to send the request to
|
|
117
|
-
* `options` **{Object}**
|
|
118
|
-
Extends [http(s).RequestOptions](https://nodejs.org/api/https.html#httpsrequesturl-options-callback) along with
|
|
119
|
-
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2connectauthority-options-listener)
|
|
120
|
-
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
|
|
121
|
-
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
|
|
122
|
-
for HTTP/2 attunes
|
|
123
|
-
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
|
|
124
|
-
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
125
|
-
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
126
|
-
with the request
|
|
127
|
-
* `cookies` **{boolean | Array<[k, v]> | Array<string\> | Cookies | Object | URLSearchParams}** `Default: true` The
|
|
128
|
-
cookies to add to
|
|
129
|
-
the request
|
|
130
|
-
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
|
|
131
|
-
* `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
|
|
132
|
-
redirects
|
|
133
|
-
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or simply add a mixin
|
|
134
|
-
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
135
|
-
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
136
|
-
* `headers` **{Object}** The headers to add to the request
|
|
137
|
-
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
138
|
-
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
|
|
139
|
-
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
140
|
-
* `retry` **{Object}** Represents the retry options
|
|
141
|
-
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
142
|
-
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
143
|
-
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
|
|
144
|
-
* `errorCodes` **{string[]}**
|
|
145
|
-
`Default: ['EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'EPIPE', 'ERR_HTTP2_STREAM_ERROR']`
|
|
146
|
-
The list of error codes to retry on
|
|
147
|
-
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
148
|
-
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
149
|
-
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
|
|
150
|
-
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
|
|
151
|
-
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
152
|
-
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
153
|
-
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL
|
|
154
|
-
* **Returns:** Promise that resolves to
|
|
155
|
-
extended [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
|
|
156
|
-
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#class-clienthttp2stream) which is respectively
|
|
157
|
-
readable and duplex streams
|
|
158
|
-
* if `digest: true` & `parse: true`
|
|
159
|
-
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
160
|
-
* if `digest: false`
|
|
161
|
-
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
|
|
162
|
-
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
|
|
163
|
-
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
|
|
164
|
-
* `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**
|
|
165
|
-
* `json` **{AsyncFunction}** Reads the response and returns **Object**
|
|
166
|
-
* `text` **{AsyncFunction}** Reads the response and returns **String**
|
|
167
|
-
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
|
|
168
|
-
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
169
|
-
* `headers` **{Object}** The headers received with the response
|
|
170
|
-
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
|
|
171
|
-
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
172
|
-
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
173
|
-
* `statusCode` **{number}** Indicates the status code of the response
|
|
174
|
-
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
#### `rekwest.defaults`
|
|
179
|
-
|
|
180
|
-
The object to fulfill with default [options](#rekwesturl-options).
|
|
181
|
-
|
|
182
|
-
---
|
|
183
|
-
|
|
184
|
-
#### `rekwest.extend(options)`
|
|
185
|
-
|
|
186
|
-
The method to extend default [options](#rekwesturl-options) per instance.
|
|
187
|
-
|
|
188
|
-
```javascript
|
|
189
|
-
import rekwest, { constants } from 'rekwest';
|
|
190
|
-
|
|
191
|
-
const {
|
|
192
|
-
HTTP_STATUS_OK,
|
|
193
|
-
} = constants;
|
|
194
|
-
|
|
195
|
-
const rk = rekwest.extend({
|
|
196
|
-
baseURL: 'https://somewhe.re',
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const signal = AbortSignal.timeout(1e4);
|
|
200
|
-
const url = '/somewhat/endpoint';
|
|
201
|
-
|
|
202
|
-
const res = await rk(url, {
|
|
203
|
-
signal,
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
207
|
-
console.info(res.headers);
|
|
208
|
-
console.log(res.body);
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
---
|
|
212
|
-
|
|
213
|
-
#### `rekwest.stream(url[, options])`
|
|
214
|
-
|
|
215
|
-
The method with limited functionality to use with streams and/or pipes.
|
|
216
|
-
|
|
217
|
-
* No automata (redirects & retries)
|
|
218
|
-
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
219
|
-
* Use `ackn({ url: URL })` method in advance to check the available protocols
|
|
220
|
-
|
|
221
|
-
```javascript
|
|
222
|
-
import fs from 'node:fs';
|
|
223
|
-
import { pipeline } from 'node:stream/promises';
|
|
224
|
-
import rekwest, {
|
|
225
|
-
ackn,
|
|
226
|
-
constants,
|
|
227
|
-
} from 'rekwest';
|
|
228
|
-
|
|
229
|
-
const {
|
|
230
|
-
HTTP2_METHOD_POST,
|
|
231
|
-
} = constants;
|
|
232
|
-
|
|
233
|
-
const url = new URL('https://somewhe.re/somewhat/endpoint');
|
|
234
|
-
const options = await ackn({ url });
|
|
235
|
-
|
|
236
|
-
await pipeline(
|
|
237
|
-
fs.createReadStream('/path/to/read/inlet.xyz'),
|
|
238
|
-
rekwest.stream(url, { ...options, method: HTTP2_METHOD_POST }),
|
|
239
|
-
fs.createWriteStream('/path/to/write/outlet.xyz'),
|
|
240
|
-
);
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
---
|
|
244
|
-
|
|
245
|
-
For more details, please check tests (coverage: **>97%**) in the repository.
|
|
1
|
+
The robust request library that humanity deserves 🌐
|
|
2
|
+
---
|
|
3
|
+
This package provides highly likely functional and **easy-to-use** abstraction atop of
|
|
4
|
+
native [http(s).request](https://nodejs.org/api/https.html#httpsrequesturl-options-callback)
|
|
5
|
+
and [http2.request](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options).
|
|
6
|
+
|
|
7
|
+
## Abstract
|
|
8
|
+
|
|
9
|
+
* Fetch-alike 🥏
|
|
10
|
+
* Cool-beans 🫐 config options (with defaults)
|
|
11
|
+
* Automatic HTTP/2 support (ALPN negotiation) 💼
|
|
12
|
+
* Automatic or opt-in body parse (with non-UTF-8 charset decoding) 🉑
|
|
13
|
+
* Automatic and simplistic `Cookies` treatment (with built-in **jar** & **ttl**) 🍪
|
|
14
|
+
* Automatic decompression (with opt-in body compression) 🗜️
|
|
15
|
+
* Built-in streamable `FormData` interface 🔌
|
|
16
|
+
* Support redirects & retries with fine-grained tune-ups 🪛
|
|
17
|
+
* Support all legit request body types (include blobs & streams) 📦
|
|
18
|
+
* Support both CJS and ESM module systems 🧩
|
|
19
|
+
* Fully promise-able and pipe-able 🔗
|
|
20
|
+
* Zero dependencies 🗽
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
* Node.js `>= 18.13.0`
|
|
25
|
+
|
|
26
|
+
## Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install rekwest --save
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Usage
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
import rekwest, { constants } from 'rekwest';
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
39
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
40
|
+
HTTP2_METHOD_POST,
|
|
41
|
+
HTTP_STATUS_OK,
|
|
42
|
+
} = constants;
|
|
43
|
+
|
|
44
|
+
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
45
|
+
|
|
46
|
+
const res = await rekwest(url, {
|
|
47
|
+
body: { celestial: 'payload' },
|
|
48
|
+
headers: {
|
|
49
|
+
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
50
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'br', // enables: body compression
|
|
51
|
+
/** [HTTP2_HEADER_CONTENT_TYPE]
|
|
52
|
+
* is undue for
|
|
53
|
+
* Array/Blob/File/FormData/Object/URLSearchParams body types
|
|
54
|
+
* and will be set automatically, with an option to override it here
|
|
55
|
+
*/
|
|
56
|
+
},
|
|
57
|
+
method: HTTP2_METHOD_POST,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
61
|
+
console.info(res.headers);
|
|
62
|
+
console.log(res.body);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
import { Readable } from 'node:stream';
|
|
69
|
+
import rekwest, {
|
|
70
|
+
constants,
|
|
71
|
+
Blob,
|
|
72
|
+
File,
|
|
73
|
+
FormData,
|
|
74
|
+
} from 'rekwest';
|
|
75
|
+
|
|
76
|
+
const {
|
|
77
|
+
HTTP2_HEADER_AUTHORIZATION,
|
|
78
|
+
HTTP2_HEADER_CONTENT_ENCODING,
|
|
79
|
+
HTTP2_METHOD_POST,
|
|
80
|
+
HTTP_STATUS_OK,
|
|
81
|
+
} = constants;
|
|
82
|
+
|
|
83
|
+
const blob = new Blob(['bits']);
|
|
84
|
+
const file = new File(['bits'], 'file.dab');
|
|
85
|
+
const readable = Readable.from('bits');
|
|
86
|
+
|
|
87
|
+
const fd = new FormData({
|
|
88
|
+
aux: Date.now(), // either [[key, value]] or kv sequenceable
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
fd.append('celestial', 'payload');
|
|
92
|
+
fd.append('blob', blob, 'blob.dab');
|
|
93
|
+
fd.append('file', file);
|
|
94
|
+
fd.append('readable', readable, 'readable.dab');
|
|
95
|
+
|
|
96
|
+
const url = 'https://somewhe.re/somewhat/endpoint';
|
|
97
|
+
|
|
98
|
+
const res = await rekwest(url, {
|
|
99
|
+
body: fd,
|
|
100
|
+
headers: {
|
|
101
|
+
[HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
|
|
102
|
+
[HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // enables: body compression
|
|
103
|
+
},
|
|
104
|
+
method: HTTP2_METHOD_POST,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
108
|
+
console.info(res.headers);
|
|
109
|
+
console.log(res.body);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### API
|
|
113
|
+
|
|
114
|
+
#### `rekwest(url[, options])`
|
|
115
|
+
|
|
116
|
+
* `url` **{string | URL}** The URL to send the request to
|
|
117
|
+
* `options` **{Object}**
|
|
118
|
+
Extends [http(s).RequestOptions](https://nodejs.org/api/https.html#httpsrequesturl-options-callback) along with
|
|
119
|
+
extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2connectauthority-options-listener)
|
|
120
|
+
& [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
|
|
121
|
+
and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
|
|
122
|
+
for HTTP/2 attunes
|
|
123
|
+
* `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
|
|
124
|
+
* `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
|
|
125
|
+
FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
|
|
126
|
+
with the request
|
|
127
|
+
* `cookies` **{boolean | Array<[k, v]> | Array<string\> | Cookies | Object | URLSearchParams}** `Default: true` The
|
|
128
|
+
cookies to add to
|
|
129
|
+
the request
|
|
130
|
+
* `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
|
|
131
|
+
* `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
|
|
132
|
+
redirects
|
|
133
|
+
* `digest` **{boolean}** `Default: true` Controls whether to read the response stream or simply add a mixin
|
|
134
|
+
* `follow` **{number}** `Default: 20` The number of redirects to follow
|
|
135
|
+
* `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
|
|
136
|
+
* `headers` **{Object}** The headers to add to the request
|
|
137
|
+
* `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
|
|
138
|
+
* `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
|
|
139
|
+
* `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
|
|
140
|
+
* `retry` **{Object}** Represents the retry options
|
|
141
|
+
* `attempts` **{number}** `Default: 0` The number of retry attempts
|
|
142
|
+
* `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
|
|
143
|
+
The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
|
|
144
|
+
* `errorCodes` **{string[]}**
|
|
145
|
+
`Default: ['EAI_AGAIN', 'ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'EPIPE', 'ERR_HTTP2_STREAM_ERROR']`
|
|
146
|
+
The list of error codes to retry on
|
|
147
|
+
* `interval` **{number}** `Default: 1e3` The initial retry interval
|
|
148
|
+
* `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
|
|
149
|
+
* `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
|
|
150
|
+
* `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
|
|
151
|
+
* `thenable` **{boolean}** `Default: false` Controls the promise resolutions
|
|
152
|
+
* `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
|
|
153
|
+
* `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL
|
|
154
|
+
* **Returns:** Promise that resolves to
|
|
155
|
+
extended [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
|
|
156
|
+
or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#class-clienthttp2stream) which is respectively
|
|
157
|
+
readable and duplex streams
|
|
158
|
+
* if `digest: true` & `parse: true`
|
|
159
|
+
* `body` **{string | Array | Buffer | Object}** The body based on its content type
|
|
160
|
+
* if `digest: false`
|
|
161
|
+
* `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
|
|
162
|
+
* `blob` **{AsyncFunction}** Reads the response and returns **Blob**
|
|
163
|
+
* `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
|
|
164
|
+
* `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**
|
|
165
|
+
* `json` **{AsyncFunction}** Reads the response and returns **Object**
|
|
166
|
+
* `text` **{AsyncFunction}** Reads the response and returns **String**
|
|
167
|
+
* `bodyUsed` **{boolean}** Indicates whether the response were read or not
|
|
168
|
+
* `cookies` **{undefined | Cookies}** The cookies sent and received with the response
|
|
169
|
+
* `headers` **{Object}** The headers received with the response
|
|
170
|
+
* `httpVersion` **{string}** Indicates protocol version negotiated with the server
|
|
171
|
+
* `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
|
|
172
|
+
* `redirected` **{boolean}** Indicates if the response is the result of a redirect
|
|
173
|
+
* `statusCode` **{number}** Indicates the status code of the response
|
|
174
|
+
* `trailers` **{undefined | Object}** The trailer headers received with the response
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
#### `rekwest.defaults`
|
|
179
|
+
|
|
180
|
+
The object to fulfill with default [options](#rekwesturl-options).
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
#### `rekwest.extend(options)`
|
|
185
|
+
|
|
186
|
+
The method to extend default [options](#rekwesturl-options) per instance.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
import rekwest, { constants } from 'rekwest';
|
|
190
|
+
|
|
191
|
+
const {
|
|
192
|
+
HTTP_STATUS_OK,
|
|
193
|
+
} = constants;
|
|
194
|
+
|
|
195
|
+
const rk = rekwest.extend({
|
|
196
|
+
baseURL: 'https://somewhe.re',
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const signal = AbortSignal.timeout(1e4);
|
|
200
|
+
const url = '/somewhat/endpoint';
|
|
201
|
+
|
|
202
|
+
const res = await rk(url, {
|
|
203
|
+
signal,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
console.assert(res.statusCode === HTTP_STATUS_OK);
|
|
207
|
+
console.info(res.headers);
|
|
208
|
+
console.log(res.body);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
#### `rekwest.stream(url[, options])`
|
|
214
|
+
|
|
215
|
+
The method with limited functionality to use with streams and/or pipes.
|
|
216
|
+
|
|
217
|
+
* No automata (redirects & retries)
|
|
218
|
+
* Pass `h2: true` in options to use HTTP/2 protocol
|
|
219
|
+
* Use `ackn({ url: URL })` method in advance to check the available protocols
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
import fs from 'node:fs';
|
|
223
|
+
import { pipeline } from 'node:stream/promises';
|
|
224
|
+
import rekwest, {
|
|
225
|
+
ackn,
|
|
226
|
+
constants,
|
|
227
|
+
} from 'rekwest';
|
|
228
|
+
|
|
229
|
+
const {
|
|
230
|
+
HTTP2_METHOD_POST,
|
|
231
|
+
} = constants;
|
|
232
|
+
|
|
233
|
+
const url = new URL('https://somewhe.re/somewhat/endpoint');
|
|
234
|
+
const options = await ackn({ url });
|
|
235
|
+
|
|
236
|
+
await pipeline(
|
|
237
|
+
fs.createReadStream('/path/to/read/inlet.xyz'),
|
|
238
|
+
rekwest.stream(url, { ...options, method: HTTP2_METHOD_POST }),
|
|
239
|
+
fs.createWriteStream('/path/to/write/outlet.xyz'),
|
|
240
|
+
);
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
For more details, please check tests (coverage: **>97%**) in the repository.
|
|
@@ -3,12 +3,16 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.default = void 0;
|
|
6
|
+
exports.isZstdSupported = exports.default = void 0;
|
|
7
7
|
var _nodeHttp = _interopRequireDefault(require("node:http2"));
|
|
8
|
+
var _nodeZlib = _interopRequireDefault(require("node:zlib"));
|
|
8
9
|
var _constants = require("./constants");
|
|
10
|
+
var _mediatypes = require("./mediatypes");
|
|
9
11
|
var _utils = require("./utils");
|
|
10
12
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
13
|
const {
|
|
14
|
+
HTTP2_HEADER_ACCEPT,
|
|
15
|
+
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
12
16
|
HTTP2_METHOD_GET,
|
|
13
17
|
HTTP_STATUS_BAD_GATEWAY,
|
|
14
18
|
HTTP_STATUS_GATEWAY_TIMEOUT,
|
|
@@ -16,12 +20,17 @@ const {
|
|
|
16
20
|
HTTP_STATUS_SERVICE_UNAVAILABLE,
|
|
17
21
|
HTTP_STATUS_TOO_MANY_REQUESTS
|
|
18
22
|
} = _nodeHttp.default.constants;
|
|
19
|
-
const
|
|
23
|
+
const isZstdSupported = exports.isZstdSupported = !!_nodeZlib.default.constants.ZSTD_CLEVEL_DEFAULT;
|
|
24
|
+
const defaults = {
|
|
20
25
|
cookiesTTL: false,
|
|
21
26
|
credentials: _constants.requestCredentials.sameOrigin,
|
|
22
27
|
digest: true,
|
|
23
28
|
follow: 20,
|
|
24
29
|
h2: false,
|
|
30
|
+
headers: {
|
|
31
|
+
[HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
|
|
32
|
+
[HTTP2_HEADER_ACCEPT_ENCODING]: `br, ${isZstdSupported ? 'zstd,' : ''} gzip, deflate, deflate-raw`
|
|
33
|
+
},
|
|
25
34
|
get maxRetryAfter() {
|
|
26
35
|
return this[_utils.maxRetryAfter] ?? this.timeout;
|
|
27
36
|
},
|
|
@@ -46,5 +55,5 @@ const stash = {
|
|
|
46
55
|
trimTrailingSlashes: false
|
|
47
56
|
};
|
|
48
57
|
var _default = exports.default = {
|
|
49
|
-
|
|
58
|
+
defaults
|
|
50
59
|
};
|
package/dist/index.js
CHANGED
|
@@ -32,6 +32,7 @@ exports.mediatypes = void 0;
|
|
|
32
32
|
var _nodeHttp = _interopRequireDefault(require("node:http"));
|
|
33
33
|
var _nodeHttp2 = _interopRequireWildcard(require("node:http2"));
|
|
34
34
|
var _nodeHttps = _interopRequireDefault(require("node:https"));
|
|
35
|
+
var _config = _interopRequireDefault(require("./config"));
|
|
35
36
|
var _constants = require("./constants");
|
|
36
37
|
Object.keys(_constants).forEach(function (key) {
|
|
37
38
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -44,7 +45,6 @@ Object.keys(_constants).forEach(function (key) {
|
|
|
44
45
|
}
|
|
45
46
|
});
|
|
46
47
|
});
|
|
47
|
-
var _defaults = _interopRequireDefault(require("./defaults"));
|
|
48
48
|
var _mediatypes2 = _interopRequireWildcard(require("./mediatypes"));
|
|
49
49
|
var _mediatypes = _mediatypes2;
|
|
50
50
|
exports.mediatypes = _mediatypes2;
|
|
@@ -147,10 +147,10 @@ function rekwest(url, options) {
|
|
|
147
147
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
148
148
|
enumerable: true,
|
|
149
149
|
get() {
|
|
150
|
-
return
|
|
150
|
+
return _config.default.defaults;
|
|
151
151
|
},
|
|
152
152
|
set(value) {
|
|
153
|
-
|
|
153
|
+
_config.default.defaults = (0, _utils.copyWithMerge)(_config.default.defaults, value);
|
|
154
154
|
}
|
|
155
155
|
});
|
|
156
156
|
Reflect.defineProperty(rekwest, 'extend', {
|
package/dist/preflight.js
CHANGED
|
@@ -5,12 +5,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.preflight = void 0;
|
|
7
7
|
var _nodeHttp = _interopRequireDefault(require("node:http2"));
|
|
8
|
+
var _config = require("./config");
|
|
8
9
|
var _constants = require("./constants");
|
|
9
10
|
var _cookies = require("./cookies");
|
|
10
|
-
var _mediatypes = require("./mediatypes");
|
|
11
11
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
12
|
const {
|
|
13
|
-
HTTP2_HEADER_ACCEPT,
|
|
14
13
|
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
15
14
|
HTTP2_HEADER_AUTHORITY,
|
|
16
15
|
HTTP2_HEADER_AUTHORIZATION,
|
|
@@ -66,9 +65,16 @@ const preflight = options => {
|
|
|
66
65
|
url.password = url.username = '';
|
|
67
66
|
}
|
|
68
67
|
options.headers = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => {
|
|
69
|
+
acc[key.toLowerCase()] = val;
|
|
70
|
+
if (acc[HTTP2_HEADER_ACCEPT_ENCODING]?.match(/\bzstd\b/i) && !_config.isZstdSupported) {
|
|
71
|
+
acc[HTTP2_HEADER_ACCEPT_ENCODING] = val.replace(/\s?zstd,?/i, '').trim();
|
|
72
|
+
if (!acc[HTTP2_HEADER_ACCEPT_ENCODING]) {
|
|
73
|
+
Reflect.deleteProperty(acc, HTTP2_HEADER_ACCEPT_ENCODING);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return acc;
|
|
77
|
+
}, {}),
|
|
72
78
|
...(h2 && {
|
|
73
79
|
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
74
80
|
[HTTP2_HEADER_METHOD]: method,
|
package/dist/utils.js
CHANGED
|
@@ -10,8 +10,10 @@ var _nodeBuffer = require("node:buffer");
|
|
|
10
10
|
var _nodeHttp = _interopRequireDefault(require("node:http2"));
|
|
11
11
|
var _nodeStream = require("node:stream");
|
|
12
12
|
var _nodeZlib = _interopRequireDefault(require("node:zlib"));
|
|
13
|
-
var
|
|
13
|
+
var _config = _interopRequireWildcard(require("./config"));
|
|
14
14
|
var _errors = require("./errors");
|
|
15
|
+
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
|
|
16
|
+
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
17
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
16
18
|
const {
|
|
17
19
|
HTTP2_HEADER_RETRY_AFTER,
|
|
@@ -75,6 +77,8 @@ const compress = (readable, encodings = '') => {
|
|
|
75
77
|
encoders.push(_nodeZlib.default.createDeflateRaw());
|
|
76
78
|
} else if (/\bgzip\b/i.test(encoding)) {
|
|
77
79
|
encoders.push(_nodeZlib.default.createGzip());
|
|
80
|
+
} else if (_config.isZstdSupported && /\bzstd\b/i.test(encoding)) {
|
|
81
|
+
encoders.push(_nodeZlib.default.createZstdCompress());
|
|
78
82
|
} else {
|
|
79
83
|
return readable;
|
|
80
84
|
}
|
|
@@ -102,6 +106,8 @@ const decompress = (readable, encodings = '') => {
|
|
|
102
106
|
decoders.push(_nodeZlib.default.createInflateRaw());
|
|
103
107
|
} else if (/\bgzip\b/i.test(encoding)) {
|
|
104
108
|
decoders.push(_nodeZlib.default.createGunzip());
|
|
109
|
+
} else if (_config.isZstdSupported && /\bzstd\b/i.test(encoding)) {
|
|
110
|
+
decoders.push(_nodeZlib.default.createZstdDecompress());
|
|
105
111
|
} else {
|
|
106
112
|
return readable;
|
|
107
113
|
}
|
|
@@ -148,7 +154,7 @@ const merge = (target, ...rest) => {
|
|
|
148
154
|
exports.merge = merge;
|
|
149
155
|
const normalize = (url, options = {}) => {
|
|
150
156
|
if (!options.redirected) {
|
|
151
|
-
options = copyWithMerge(
|
|
157
|
+
options = copyWithMerge(_config.default.defaults, options);
|
|
152
158
|
}
|
|
153
159
|
if (options.trimTrailingSlashes) {
|
|
154
160
|
url = `${url}`.replace(/(?<!:)\/+/g, '/');
|
package/package.json
CHANGED
|
@@ -9,14 +9,13 @@
|
|
|
9
9
|
},
|
|
10
10
|
"devDependencies": {
|
|
11
11
|
"@babel/cli": "^7.26.4",
|
|
12
|
-
"@babel/core": "^7.26.
|
|
13
|
-
"@babel/eslint-parser": "^7.
|
|
14
|
-
"@babel/preset-env": "^7.26.
|
|
15
|
-
"@stylistic/eslint-plugin-js": "^2.12.1",
|
|
12
|
+
"@babel/core": "^7.26.9",
|
|
13
|
+
"@babel/eslint-parser": "^7.26.8",
|
|
14
|
+
"@babel/preset-env": "^7.26.9",
|
|
16
15
|
"c8": "^10.1.3",
|
|
17
|
-
"eslint": "^9.
|
|
18
|
-
"eslint-config-ultra-refined": "^3.
|
|
19
|
-
"mocha": "^11.0
|
|
16
|
+
"eslint": "^9.20.1",
|
|
17
|
+
"eslint-config-ultra-refined": "^3.4.2",
|
|
18
|
+
"mocha": "^11.1.0"
|
|
20
19
|
},
|
|
21
20
|
"description": "The robust request library that humanity deserves 🌐",
|
|
22
21
|
"engines": {
|
|
@@ -37,6 +36,7 @@
|
|
|
37
36
|
"brotli",
|
|
38
37
|
"cookie",
|
|
39
38
|
"deflate",
|
|
39
|
+
"deflate-raw",
|
|
40
40
|
"fetch",
|
|
41
41
|
"formdata",
|
|
42
42
|
"gzip",
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
"retry",
|
|
52
52
|
"retry-after",
|
|
53
53
|
"stream",
|
|
54
|
-
"upload"
|
|
54
|
+
"upload",
|
|
55
|
+
"zstd"
|
|
55
56
|
],
|
|
56
57
|
"license": "MIT",
|
|
57
58
|
"name": "rekwest",
|
|
@@ -70,5 +71,5 @@
|
|
|
70
71
|
"test:bail": "mocha --bail",
|
|
71
72
|
"test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
|
|
72
73
|
},
|
|
73
|
-
"version": "5.
|
|
74
|
+
"version": "5.4.0"
|
|
74
75
|
}
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
import http2 from 'node:http2';
|
|
2
|
+
import zlib from 'node:zlib';
|
|
2
3
|
import {
|
|
3
4
|
requestCredentials,
|
|
4
5
|
requestRedirect,
|
|
5
6
|
} from './constants.mjs';
|
|
7
|
+
import {
|
|
8
|
+
APPLICATION_JSON,
|
|
9
|
+
TEXT_PLAIN,
|
|
10
|
+
WILDCARD,
|
|
11
|
+
} from './mediatypes.mjs';
|
|
6
12
|
import { maxRetryAfter } from './utils.mjs';
|
|
7
13
|
|
|
8
14
|
const {
|
|
15
|
+
HTTP2_HEADER_ACCEPT,
|
|
16
|
+
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
9
17
|
HTTP2_METHOD_GET,
|
|
10
18
|
HTTP_STATUS_BAD_GATEWAY,
|
|
11
19
|
HTTP_STATUS_GATEWAY_TIMEOUT,
|
|
@@ -14,12 +22,18 @@ const {
|
|
|
14
22
|
HTTP_STATUS_TOO_MANY_REQUESTS,
|
|
15
23
|
} = http2.constants;
|
|
16
24
|
|
|
17
|
-
const
|
|
25
|
+
export const isZstdSupported = !!zlib.constants.ZSTD_CLEVEL_DEFAULT;
|
|
26
|
+
|
|
27
|
+
const defaults = {
|
|
18
28
|
cookiesTTL: false,
|
|
19
29
|
credentials: requestCredentials.sameOrigin,
|
|
20
30
|
digest: true,
|
|
21
31
|
follow: 20,
|
|
22
32
|
h2: false,
|
|
33
|
+
headers: {
|
|
34
|
+
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
35
|
+
[HTTP2_HEADER_ACCEPT_ENCODING]: `br, ${ isZstdSupported ? 'zstd,' : '' } gzip, deflate, deflate-raw`,
|
|
36
|
+
},
|
|
23
37
|
get maxRetryAfter() {
|
|
24
38
|
return this[maxRetryAfter] ?? this.timeout;
|
|
25
39
|
},
|
|
@@ -62,5 +76,5 @@ const stash = {
|
|
|
62
76
|
};
|
|
63
77
|
|
|
64
78
|
export default {
|
|
65
|
-
|
|
79
|
+
defaults,
|
|
66
80
|
};
|
package/src/formdata.mjs
CHANGED
|
@@ -1,244 +1,246 @@
|
|
|
1
|
-
import { File } from 'node:buffer';
|
|
2
|
-
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import http2 from 'node:http2';
|
|
4
|
-
import { toUSVString } from 'node:util';
|
|
5
|
-
import {
|
|
6
|
-
APPLICATION_OCTET_STREAM,
|
|
7
|
-
MULTIPART_FORM_DATA,
|
|
8
|
-
} from './mediatypes.mjs';
|
|
9
|
-
import {
|
|
10
|
-
brandCheck,
|
|
11
|
-
isFileLike,
|
|
12
|
-
tap,
|
|
13
|
-
} from './utils.mjs';
|
|
14
|
-
|
|
15
|
-
const CRLF = '\r\n';
|
|
16
|
-
const {
|
|
17
|
-
HTTP2_HEADER_CONTENT_DISPOSITION,
|
|
18
|
-
HTTP2_HEADER_CONTENT_TYPE,
|
|
19
|
-
} = http2.constants;
|
|
20
|
-
|
|
21
|
-
export class FormData {
|
|
22
|
-
|
|
23
|
-
static actuate(fd) {
|
|
24
|
-
const boundary = randomBytes(24).toString('hex');
|
|
25
|
-
const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
|
|
26
|
-
const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
|
|
27
|
-
|
|
28
|
-
const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
|
|
29
|
-
const redress = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
contentType,
|
|
33
|
-
async* [Symbol.asyncIterator]() {
|
|
34
|
-
const encoder = new TextEncoder();
|
|
35
|
-
|
|
36
|
-
for (const [name, value] of fd) {
|
|
37
|
-
if (value.constructor === String) {
|
|
38
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
39
|
-
escape(redress(name))
|
|
40
|
-
}"${ CRLF.repeat(2) }${ redress(value) }${ CRLF }`);
|
|
41
|
-
} else {
|
|
42
|
-
yield encoder.encode(`${ prefix }; name="${
|
|
43
|
-
escape(redress(name))
|
|
44
|
-
}"${ value.name ? `; filename="${ escape(value.name) }"` : '' }${ CRLF }${
|
|
45
|
-
HTTP2_HEADER_CONTENT_TYPE
|
|
46
|
-
}: ${
|
|
47
|
-
value.type || APPLICATION_OCTET_STREAM
|
|
48
|
-
}${ CRLF.repeat(2) }`);
|
|
49
|
-
yield* tap(value);
|
|
50
|
-
yield new Uint8Array([
|
|
51
|
-
13,
|
|
52
|
-
10,
|
|
53
|
-
]);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
yield encoder.encode(`--${ boundary }--`);
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
static alike(instance) {
|
|
63
|
-
return FormData.name === instance?.[Symbol.toStringTag];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
static #enfoldEntry(name, value, filename) {
|
|
67
|
-
name = toUSVString(name);
|
|
68
|
-
filename &&= toUSVString(filename);
|
|
69
|
-
|
|
70
|
-
if (isFileLike(value)) {
|
|
71
|
-
filename ??= value.name || 'blob';
|
|
72
|
-
value = new File([value], filename, value);
|
|
73
|
-
} else if (this.#ensureInstance(value)) {
|
|
74
|
-
value.name = filename;
|
|
75
|
-
} else {
|
|
76
|
-
value = toUSVString(value);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return {
|
|
80
|
-
name,
|
|
81
|
-
value,
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
static #ensureInstance(value) {
|
|
86
|
-
return isFileLike(value) || (value === Object(value) && Reflect.has(value, Symbol.asyncIterator));
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#entries = [];
|
|
90
|
-
|
|
91
|
-
get [Symbol.toStringTag]() {
|
|
92
|
-
return this.constructor.name;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
constructor(input) {
|
|
96
|
-
if (input === Object(input)
|
|
97
|
-
&& (input?.constructor === Object || Reflect.has(input, Symbol.iterator))) {
|
|
98
|
-
|
|
99
|
-
if (input.constructor !== Object) {
|
|
100
|
-
input = Array.from(input);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (Array.isArray(input)) {
|
|
104
|
-
if (!input.every((it) => Array.isArray(it))) {
|
|
105
|
-
throw new TypeError(`Failed to construct '${
|
|
106
|
-
this[Symbol.toStringTag]
|
|
107
|
-
}': The provided value cannot be converted to a sequence.`);
|
|
108
|
-
} else if (!input.every((it) => it.length === 2)) {
|
|
109
|
-
throw new TypeError(`Failed to construct '${
|
|
110
|
-
this[Symbol.toStringTag]
|
|
111
|
-
}': Sequence initializer must only contain pair elements.`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (input.constructor === Object) {
|
|
116
|
-
input = Object.entries(input);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
for (const [key, value] of input) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
this.#entries.
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
1
|
+
import { File } from 'node:buffer';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import http2 from 'node:http2';
|
|
4
|
+
import { toUSVString } from 'node:util';
|
|
5
|
+
import {
|
|
6
|
+
APPLICATION_OCTET_STREAM,
|
|
7
|
+
MULTIPART_FORM_DATA,
|
|
8
|
+
} from './mediatypes.mjs';
|
|
9
|
+
import {
|
|
10
|
+
brandCheck,
|
|
11
|
+
isFileLike,
|
|
12
|
+
tap,
|
|
13
|
+
} from './utils.mjs';
|
|
14
|
+
|
|
15
|
+
const CRLF = '\r\n';
|
|
16
|
+
const {
|
|
17
|
+
HTTP2_HEADER_CONTENT_DISPOSITION,
|
|
18
|
+
HTTP2_HEADER_CONTENT_TYPE,
|
|
19
|
+
} = http2.constants;
|
|
20
|
+
|
|
21
|
+
export class FormData {
|
|
22
|
+
|
|
23
|
+
static actuate(fd) {
|
|
24
|
+
const boundary = randomBytes(24).toString('hex');
|
|
25
|
+
const contentType = `${ MULTIPART_FORM_DATA }; boundary=${ boundary }`;
|
|
26
|
+
const prefix = `--${ boundary }${ CRLF }${ HTTP2_HEADER_CONTENT_DISPOSITION }: form-data`;
|
|
27
|
+
|
|
28
|
+
const escape = (str) => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
|
|
29
|
+
const redress = (str) => str.replace(/\r?\n|\r/g, CRLF);
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
contentType,
|
|
33
|
+
async* [Symbol.asyncIterator]() {
|
|
34
|
+
const encoder = new TextEncoder();
|
|
35
|
+
|
|
36
|
+
for (const [name, value] of fd) {
|
|
37
|
+
if (value.constructor === String) {
|
|
38
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
39
|
+
escape(redress(name))
|
|
40
|
+
}"${ CRLF.repeat(2) }${ redress(value) }${ CRLF }`);
|
|
41
|
+
} else {
|
|
42
|
+
yield encoder.encode(`${ prefix }; name="${
|
|
43
|
+
escape(redress(name))
|
|
44
|
+
}"${ value.name ? `; filename="${ escape(value.name) }"` : '' }${ CRLF }${
|
|
45
|
+
HTTP2_HEADER_CONTENT_TYPE
|
|
46
|
+
}: ${
|
|
47
|
+
value.type || APPLICATION_OCTET_STREAM
|
|
48
|
+
}${ CRLF.repeat(2) }`);
|
|
49
|
+
yield* tap(value);
|
|
50
|
+
yield new Uint8Array([
|
|
51
|
+
13,
|
|
52
|
+
10,
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
yield encoder.encode(`--${ boundary }--`);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static alike(instance) {
|
|
63
|
+
return FormData.name === instance?.[Symbol.toStringTag];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static #enfoldEntry(name, value, filename) {
|
|
67
|
+
name = toUSVString(name);
|
|
68
|
+
filename &&= toUSVString(filename);
|
|
69
|
+
|
|
70
|
+
if (isFileLike(value)) {
|
|
71
|
+
filename ??= value.name || 'blob';
|
|
72
|
+
value = new File([value], filename, value);
|
|
73
|
+
} else if (this.#ensureInstance(value)) {
|
|
74
|
+
value.name = filename;
|
|
75
|
+
} else {
|
|
76
|
+
value = toUSVString(value);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
name,
|
|
81
|
+
value,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
static #ensureInstance(value) {
|
|
86
|
+
return isFileLike(value) || (value === Object(value) && Reflect.has(value, Symbol.asyncIterator));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#entries = [];
|
|
90
|
+
|
|
91
|
+
get [Symbol.toStringTag]() {
|
|
92
|
+
return this.constructor.name;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
constructor(input) {
|
|
96
|
+
if (input === Object(input)
|
|
97
|
+
&& (input?.constructor === Object || Reflect.has(input, Symbol.iterator))) {
|
|
98
|
+
|
|
99
|
+
if (input.constructor !== Object) {
|
|
100
|
+
input = Array.from(input);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (Array.isArray(input)) {
|
|
104
|
+
if (!input.every((it) => Array.isArray(it))) {
|
|
105
|
+
throw new TypeError(`Failed to construct '${
|
|
106
|
+
this[Symbol.toStringTag]
|
|
107
|
+
}': The provided value cannot be converted to a sequence.`);
|
|
108
|
+
} else if (!input.every((it) => it.length === 2)) {
|
|
109
|
+
throw new TypeError(`Failed to construct '${
|
|
110
|
+
this[Symbol.toStringTag]
|
|
111
|
+
}': Sequence initializer must only contain pair elements.`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (input.constructor === Object) {
|
|
116
|
+
input = Object.entries(input);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const [key, value] of input) {
|
|
120
|
+
this.append(key, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
#ensureArgs(args, expected, method) {
|
|
126
|
+
if (args.length < expected) {
|
|
127
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
128
|
+
this[Symbol.toStringTag]
|
|
129
|
+
}': ${ expected } arguments required, but only ${ args.length } present.`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if ([
|
|
133
|
+
'append',
|
|
134
|
+
'set',
|
|
135
|
+
].includes(method)) {
|
|
136
|
+
if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
|
|
137
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
138
|
+
this[Symbol.toStringTag]
|
|
139
|
+
}': parameter ${ expected } is not of type 'Blob'.`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (method === 'forEach') {
|
|
144
|
+
if (args[0]?.constructor !== Function) {
|
|
145
|
+
throw new TypeError(`Failed to execute '${ method }' on '${
|
|
146
|
+
this[Symbol.toStringTag]
|
|
147
|
+
}': parameter ${ expected } is not of type 'Function'.`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
append(...args) {
|
|
153
|
+
brandCheck(this, FormData);
|
|
154
|
+
this.#ensureArgs(args, 2, 'append');
|
|
155
|
+
this.#entries.push(this.constructor.#enfoldEntry(...args));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
delete(...args) {
|
|
159
|
+
brandCheck(this, FormData);
|
|
160
|
+
this.#ensureArgs(args, 1, 'delete');
|
|
161
|
+
const name = toUSVString(args[0]);
|
|
162
|
+
|
|
163
|
+
this.#entries = this.#entries.filter((it) => it.name !== name);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
forEach(...args) {
|
|
167
|
+
brandCheck(this, FormData);
|
|
168
|
+
this.#ensureArgs(args, 1, 'forEach');
|
|
169
|
+
const [callback, thisArg] = args;
|
|
170
|
+
|
|
171
|
+
for (const entry of this) {
|
|
172
|
+
Reflect.apply(callback, thisArg, [
|
|
173
|
+
...entry.reverse(),
|
|
174
|
+
this,
|
|
175
|
+
]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
get(...args) {
|
|
180
|
+
brandCheck(this, FormData);
|
|
181
|
+
this.#ensureArgs(args, 1, 'get');
|
|
182
|
+
const name = toUSVString(args[0]);
|
|
183
|
+
|
|
184
|
+
return (this.#entries.find((it) => it.name === name) ?? {}).value ?? null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
getAll(...args) {
|
|
188
|
+
brandCheck(this, FormData);
|
|
189
|
+
this.#ensureArgs(args, 1, 'getAll');
|
|
190
|
+
const name = toUSVString(args[0]);
|
|
191
|
+
|
|
192
|
+
return this.#entries.filter((it) => it.name === name).map((it) => it.value);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
has(...args) {
|
|
196
|
+
brandCheck(this, FormData);
|
|
197
|
+
this.#ensureArgs(args, 1, 'has');
|
|
198
|
+
const name = toUSVString(args[0]);
|
|
199
|
+
|
|
200
|
+
return !!this.#entries.find((it) => it.name === name);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
set(...args) {
|
|
204
|
+
brandCheck(this, FormData);
|
|
205
|
+
this.#ensureArgs(args, 2, 'set');
|
|
206
|
+
const entry = this.constructor.#enfoldEntry(...args);
|
|
207
|
+
const idx = this.#entries.findIndex((it) => it.name === entry.name);
|
|
208
|
+
|
|
209
|
+
if (idx !== -1) {
|
|
210
|
+
this.#entries.splice(idx, 1, entry);
|
|
211
|
+
} else {
|
|
212
|
+
this.#entries.push(entry);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
* entries() {
|
|
217
|
+
brandCheck(this, FormData);
|
|
218
|
+
for (const { name, value } of this.#entries) {
|
|
219
|
+
yield [
|
|
220
|
+
name,
|
|
221
|
+
value,
|
|
222
|
+
];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
* keys() {
|
|
227
|
+
brandCheck(this, FormData);
|
|
228
|
+
for (const [name] of this) {
|
|
229
|
+
yield name;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
* values() {
|
|
234
|
+
brandCheck(this, FormData);
|
|
235
|
+
for (const [, value] of this) {
|
|
236
|
+
yield value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
[Symbol.iterator]() {
|
|
241
|
+
brandCheck(this, FormData);
|
|
242
|
+
|
|
243
|
+
return this.entries();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import http2 from 'node:http2';
|
|
3
3
|
import https from 'node:https';
|
|
4
|
+
import config from './config.mjs';
|
|
4
5
|
import { requestRedirect } from './constants.mjs';
|
|
5
|
-
import defaults from './defaults.mjs';
|
|
6
6
|
import { APPLICATION_OCTET_STREAM } from './mediatypes.mjs';
|
|
7
7
|
import { preflight } from './preflight.mjs';
|
|
8
8
|
import { transfer } from './transfer.mjs';
|
|
@@ -40,8 +40,8 @@ export default function rekwest(url, options) {
|
|
|
40
40
|
|
|
41
41
|
Reflect.defineProperty(rekwest, 'defaults', {
|
|
42
42
|
enumerable: true,
|
|
43
|
-
get() { return defaults
|
|
44
|
-
set(value) { defaults
|
|
43
|
+
get() { return config.defaults; },
|
|
44
|
+
set(value) { config.defaults = copyWithMerge(config.defaults, value); },
|
|
45
45
|
});
|
|
46
46
|
|
|
47
47
|
Reflect.defineProperty(rekwest, 'extend', {
|
package/src/preflight.mjs
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import http2 from 'node:http2';
|
|
2
|
+
import { isZstdSupported } from './config.mjs';
|
|
2
3
|
import { requestCredentials } from './constants.mjs';
|
|
3
4
|
import { Cookies } from './cookies.mjs';
|
|
4
|
-
import {
|
|
5
|
-
APPLICATION_JSON,
|
|
6
|
-
TEXT_PLAIN,
|
|
7
|
-
WILDCARD,
|
|
8
|
-
} from './mediatypes.mjs';
|
|
9
5
|
|
|
10
6
|
const {
|
|
11
|
-
HTTP2_HEADER_ACCEPT,
|
|
12
7
|
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
13
8
|
HTTP2_HEADER_AUTHORITY,
|
|
14
9
|
HTTP2_HEADER_AUTHORIZATION,
|
|
@@ -71,10 +66,19 @@ export const preflight = (options) => {
|
|
|
71
66
|
}
|
|
72
67
|
|
|
73
68
|
options.headers = {
|
|
74
|
-
[HTTP2_HEADER_ACCEPT]: `${ APPLICATION_JSON }, ${ TEXT_PLAIN }, ${ WILDCARD }`,
|
|
75
|
-
[HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, deflate-raw, gzip, identity',
|
|
76
69
|
...Object.entries(options.headers ?? {})
|
|
77
|
-
.reduce((acc, [key, val]) =>
|
|
70
|
+
.reduce((acc, [key, val]) => {
|
|
71
|
+
acc[key.toLowerCase()] = val;
|
|
72
|
+
|
|
73
|
+
if (acc[HTTP2_HEADER_ACCEPT_ENCODING]?.match(/\bzstd\b/i) && !isZstdSupported) {
|
|
74
|
+
acc[HTTP2_HEADER_ACCEPT_ENCODING] = val.replace(/\s?zstd,?/i, '').trim();
|
|
75
|
+
if (!acc[HTTP2_HEADER_ACCEPT_ENCODING]) {
|
|
76
|
+
Reflect.deleteProperty(acc, HTTP2_HEADER_ACCEPT_ENCODING);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return acc;
|
|
81
|
+
}, {}),
|
|
78
82
|
...h2 && {
|
|
79
83
|
[HTTP2_HEADER_AUTHORITY]: url.host,
|
|
80
84
|
[HTTP2_HEADER_METHOD]: method,
|
package/src/utils.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import http2 from 'node:http2';
|
|
6
6
|
import { pipeline } from 'node:stream';
|
|
7
7
|
import zlib from 'node:zlib';
|
|
8
|
-
import
|
|
8
|
+
import config, { isZstdSupported } from './config.mjs';
|
|
9
9
|
import {
|
|
10
10
|
RequestError,
|
|
11
11
|
TimeoutError,
|
|
@@ -79,6 +79,8 @@ export const compress = (readable, encodings = '') => {
|
|
|
79
79
|
encoders.push(zlib.createDeflateRaw());
|
|
80
80
|
} else if (/\bgzip\b/i.test(encoding)) {
|
|
81
81
|
encoders.push(zlib.createGzip());
|
|
82
|
+
} else if (isZstdSupported && /\bzstd\b/i.test(encoding)) {
|
|
83
|
+
encoders.push(zlib.createZstdCompress());
|
|
82
84
|
} else {
|
|
83
85
|
return readable;
|
|
84
86
|
}
|
|
@@ -110,6 +112,8 @@ export const decompress = (readable, encodings = '') => {
|
|
|
110
112
|
decoders.push(zlib.createInflateRaw());
|
|
111
113
|
} else if (/\bgzip\b/i.test(encoding)) {
|
|
112
114
|
decoders.push(zlib.createGunzip());
|
|
115
|
+
} else if (isZstdSupported && /\bzstd\b/i.test(encoding)) {
|
|
116
|
+
decoders.push(zlib.createZstdDecompress());
|
|
113
117
|
} else {
|
|
114
118
|
return readable;
|
|
115
119
|
}
|
|
@@ -165,7 +169,7 @@ export const merge = (target, ...rest) => {
|
|
|
165
169
|
|
|
166
170
|
export const normalize = (url, options = {}) => {
|
|
167
171
|
if (!options.redirected) {
|
|
168
|
-
options = copyWithMerge(defaults
|
|
172
|
+
options = copyWithMerge(config.defaults, options);
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
if (options.trimTrailingSlashes) {
|