rekwest 2.4.0 → 3.0.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 CHANGED
@@ -8,12 +8,12 @@ and [http2.request](https://nodejs.org/api/http2.html#http2_clienthttp2session_r
8
8
 
9
9
  * Fetch-alike
10
10
  * Cool-beans config options (with defaults)
11
- * Automatic HTTP2 support (ALPN negotiation)
11
+ * Automatic HTTP/2 support (ALPN negotiation)
12
12
  * Automatic or opt-in body parse (with non-UTF-8 charset decoding)
13
13
  * Automatic and simplistic `Cookies` treatment (with built-in jar)
14
14
  * Automatic decompression (with opt-in body compression)
15
15
  * Built-in streamable `File` & `FormData` interfaces
16
- * Support redirects with fine-grained tune-ups
16
+ * Support redirects & retries with fine-grained tune-ups
17
17
  * Support all legit request body types (include blobs & streams)
18
18
  * Support both CJS and ESM module systems
19
19
  * Fully promise-able and pipe-able
@@ -120,54 +120,63 @@ console.log(res.body);
120
120
  extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2_http2_connect_authority_options_listener)
121
121
  & [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#http2_clienthttp2session_request_headers_options)
122
122
  and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback)
123
- for the HTTP2 attunes
123
+ for HTTP/2 attunes
124
124
  * `body` **{string | Array | AsyncIterator | Blob | Buffer | File | FromData | Iterator | Object | Readable |
125
- Uint8Array | URLSearchParams}** Body to send with the request
126
- * `cookies` **{boolean | Array[[key, value]] | Cookies | Object | URLSearchParams}** `Default: true` Cookies to add to
125
+ Uint8Array | URLSearchParams}** The body to send with the request
126
+ * `cookies` **{boolean | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The cookies to add to
127
127
  the request
128
- * `digest` **{boolean}** `Default: true` Read response stream, or simply add a mixin
129
- * `follow` **{number}** `Default: 20` Number of redirects to follow
130
- * `h2` **{boolean}** `Default: false` Forces use of the HTTP2 protocol
131
- * `headers` **{Object}** Headers to add to the request
132
- * `parse` **{boolean}** `Default: true` Parse response body, or simply return a buffer
133
- * `redirect` **{error | follow | manual}** `Default: 'follow'` Controls redirect flow
134
- * `thenable` **{boolean}** `Default: false` Controls promise resolutions
128
+ * `digest` **{boolean}** `Default: true` Controls whether to read the response stream or just add a mixin
129
+ * `follow` **{number}** `Default: 20` The number of redirects to follow
130
+ * `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
131
+ * `headers` **{Object}** The headers to add to the request
132
+ * `maxRetryAfter` **{number}** The upper limit of `retry-after` header. If unset, it will use `timeout` value
133
+ * `parse` **{boolean}** `Default: true` Controls whether to parse response body or simply return a buffer
134
+ * `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
135
+ * `retry` **{Object}** Represents the retry options
136
+ * `attempts` **{number}** `Default: 0` The number of retry attempts
137
+ * `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
138
+ The backoff strategy algorithm that increases logarithmically. To fixate set value to `interval * 1`
139
+ * `interval` **{number}** `Default: 1e3` The initial retry interval
140
+ * `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
141
+ * `statusCodes` **{number[]}** `Default: [429, 503]` The list of status codes to retry on
142
+ * `thenable` **{boolean}** `Default: false` Controls the promise resolutions
143
+ * `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
135
144
  * **Returns:** Promise that resolves to
136
145
  extended [http.IncomingMessage](https://nodejs.org/api/http.html#http_class_http_incomingmessage)
137
146
  or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#http2_class_clienthttp2stream) which is respectively
138
147
  readable and duplex streams
139
148
  * if `degist: true` & `parse: true`
140
- * `body` **{string | Array | Buffer | Object}** Body based on its content type
149
+ * `body` **{string | Array | Buffer | Object}** The body based on its content type
141
150
  * if `degist: false`
142
151
  * `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
143
152
  * `blob` **{AsyncFunction}** Reads the response and returns **Blob**
144
153
  * `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
145
154
  * `json` **{AsyncFunction}** Reads the response and returns **Object**
146
155
  * `text` **{AsyncFunction}** Reads the response and returns **String**
147
- * `bodyUsed` **{boolean}** Whether the response were read or not
148
- * `cookies` **{undefined | Cookies}** Cookies sent and received with the response
149
- * `headers` **{Object}** Headers received with the response
156
+ * `bodyUsed` **{boolean}** Indicates whether the response were read or not
157
+ * `cookies` **{undefined | Cookies}** The cookies sent and received with the response
158
+ * `headers` **{Object}** The headers received with the response
150
159
  * `httpVersion` **{string}** Indicates protocol version negotiated with the server
151
160
  * `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
152
161
  * `redirected` **{boolean}** Indicates if the response is the result of a redirect
153
162
  * `statusCode` **{number}** Indicates the status code of the response
154
- * `trailers` **{undefined | Object}** Trailer headers received with the response
163
+ * `trailers` **{undefined | Object}** The trailer headers received with the response
155
164
 
156
165
  ---
157
166
 
158
167
  #### `rekwest.defaults`
159
168
 
160
- Object to fill with default [options](#rekwesturl-options)
169
+ The object to fulfill with default [options](#rekwesturl-options)
161
170
 
162
171
  ---
163
172
 
164
173
  #### `rekwest.stream(url[, options])`
165
174
 
166
- Method with limited functionality to use with streams and pipes
175
+ The method with limited functionality to use with streams and/or pipes
167
176
 
168
177
  * No automata
169
178
  * No redirects
170
- * Pass `h2: true` in options to use the HTTP2 protocol
179
+ * Pass `h2: true` in options to use HTTP/2 protocol
171
180
  * Or use `ackn({ url: URL })` method in advance to probe the available protocols
172
181
 
173
182
  ---
package/dist/errors.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.RequestError = void 0;
4
+ exports.TimeoutError = exports.RequestError = void 0;
5
5
 
6
6
  class RequestError extends Error {
7
7
  get [Symbol.toStringTag]() {
@@ -12,11 +12,15 @@ class RequestError extends Error {
12
12
  return this[Symbol.toStringTag];
13
13
  }
14
14
 
15
- constructor(message) {
16
- super(message);
15
+ constructor(...args) {
16
+ super(...args);
17
17
  Error.captureStackTrace(this, this.constructor);
18
18
  }
19
19
 
20
20
  }
21
21
 
22
- exports.RequestError = RequestError;
22
+ exports.RequestError = RequestError;
23
+
24
+ class TimeoutError extends RequestError {}
25
+
26
+ exports.TimeoutError = TimeoutError;
package/dist/helpers.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
 
3
3
  exports.__esModule = true;
4
- exports.redirects = exports.premix = exports.preflight = exports.merge = exports.dispatch = exports.decompress = exports.compress = void 0;
4
+ exports.redirects = exports.preflight = exports.mixin = exports.merge = exports.dispatch = exports.decompress = exports.compress = exports.affix = exports.admix = void 0;
5
5
  exports.tap = tap;
6
6
  exports.transform = void 0;
7
7
 
@@ -17,6 +17,8 @@ var _zlib = _interopRequireDefault(require("zlib"));
17
17
 
18
18
  var _cookies = require("./cookies.js");
19
19
 
20
+ var _errors = require("./errors.js");
21
+
20
22
  var _file = require("./file.js");
21
23
 
22
24
  var _formdata = require("./formdata.js");
@@ -36,6 +38,7 @@ const {
36
38
  HTTP2_HEADER_METHOD,
37
39
  HTTP2_HEADER_PATH,
38
40
  HTTP2_HEADER_SCHEME,
41
+ HTTP2_HEADER_STATUS,
39
42
  HTTP2_METHOD_GET,
40
43
  HTTP2_METHOD_HEAD
41
44
  } = _http.default.constants;
@@ -46,6 +49,51 @@ const gunzip = (0, _util.promisify)(_zlib.default.gunzip);
46
49
  const deflate = (0, _util.promisify)(_zlib.default.deflate);
47
50
  const inflate = (0, _util.promisify)(_zlib.default.inflate);
48
51
 
52
+ const admix = (res, headers, options) => {
53
+ const {
54
+ h2
55
+ } = options;
56
+
57
+ if (h2) {
58
+ Reflect.defineProperty(res, 'headers', {
59
+ enumerable: true,
60
+ value: headers
61
+ });
62
+ Reflect.defineProperty(res, 'httpVersion', {
63
+ enumerable: true,
64
+ value: `${h2 + 1}.0`
65
+ });
66
+ Reflect.defineProperty(res, 'statusCode', {
67
+ enumerable: true,
68
+ value: headers[HTTP2_HEADER_STATUS]
69
+ });
70
+ }
71
+
72
+ Reflect.defineProperty(res, 'ok', {
73
+ enumerable: true,
74
+ value: /^2\d{2}$/.test(res.statusCode)
75
+ });
76
+ Reflect.defineProperty(res, 'redirected', {
77
+ enumerable: true,
78
+ value: !!options.redirected
79
+ });
80
+ };
81
+
82
+ exports.admix = admix;
83
+
84
+ const affix = (client, req, options) => {
85
+ req.once('end', () => client?.close());
86
+ req.once('timeout', () => req.destroy(new _errors.TimeoutError(`Timed out after ${options.timeout} ms.`)));
87
+ req.once('trailers', trailers => {
88
+ Reflect.defineProperty(req, 'trailers', {
89
+ enumerable: true,
90
+ value: trailers
91
+ });
92
+ });
93
+ };
94
+
95
+ exports.affix = affix;
96
+
49
97
  const compress = (buf, encoding, {
50
98
  async = false
51
99
  } = {}) => {
@@ -127,73 +175,7 @@ const merge = (target = {}, ...rest) => {
127
175
 
128
176
  exports.merge = merge;
129
177
 
130
- const preflight = options => {
131
- const url = options.url = new URL(options.url);
132
- const {
133
- cookies,
134
- h2 = false,
135
- method = HTTP2_METHOD_GET,
136
- headers,
137
- redirected
138
- } = options;
139
-
140
- if (h2) {
141
- options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
142
- }
143
-
144
- if (cookies !== false) {
145
- let cookie = _cookies.Cookies.jar.get(url.origin);
146
-
147
- if (cookies === Object(cookies) && !redirected) {
148
- if (cookie) {
149
- new _cookies.Cookies(cookies).forEach(function (val, key) {
150
- this.set(key, val);
151
- }, cookie);
152
- } else {
153
- cookie = new _cookies.Cookies(cookies);
154
-
155
- _cookies.Cookies.jar.set(url.origin, cookie);
156
- }
157
- }
158
-
159
- options.headers = { ...(cookie && {
160
- [HTTP2_HEADER_COOKIE]: cookie
161
- }),
162
- ...headers
163
- };
164
- }
165
-
166
- options.digest ??= true;
167
- options.follow ??= 20;
168
- options.h2 ??= h2;
169
- options.headers = {
170
- [HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
171
- [HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
172
- ...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
173
- ...(h2 && {
174
- [HTTP2_HEADER_AUTHORITY]: url.host,
175
- [HTTP2_HEADER_METHOD]: method,
176
- [HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
177
- [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
178
- })
179
- };
180
- options.method ??= method;
181
- options.parse ??= true;
182
- options.redirect ??= redirects.follow;
183
-
184
- if (!Object.values(redirects).includes(options.redirect)) {
185
- options.createConnection?.().destroy();
186
- throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
187
- }
188
-
189
- options.redirected ??= false;
190
- options.thenable ??= false;
191
- return options;
192
- };
193
-
194
- exports.preflight = preflight;
195
-
196
- const premix = (res, {
178
+ const mixin = (res, {
197
179
  digest = false,
198
180
  parse = false
199
181
  } = {}) => {
@@ -281,7 +263,73 @@ const premix = (res, {
281
263
  });
282
264
  };
283
265
 
284
- exports.premix = premix;
266
+ exports.mixin = mixin;
267
+
268
+ const preflight = options => {
269
+ const url = options.url = new URL(options.url);
270
+ const {
271
+ cookies,
272
+ h2 = false,
273
+ method = HTTP2_METHOD_GET,
274
+ headers,
275
+ redirected
276
+ } = options;
277
+
278
+ if (h2) {
279
+ options.endStream = [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(method);
280
+ }
281
+
282
+ if (cookies !== false) {
283
+ let cookie = _cookies.Cookies.jar.get(url.origin);
284
+
285
+ if (cookies === Object(cookies) && !redirected) {
286
+ if (cookie) {
287
+ new _cookies.Cookies(cookies).forEach(function (val, key) {
288
+ this.set(key, val);
289
+ }, cookie);
290
+ } else {
291
+ cookie = new _cookies.Cookies(cookies);
292
+
293
+ _cookies.Cookies.jar.set(url.origin, cookie);
294
+ }
295
+ }
296
+
297
+ options.headers = { ...(cookie && {
298
+ [HTTP2_HEADER_COOKIE]: cookie
299
+ }),
300
+ ...headers
301
+ };
302
+ }
303
+
304
+ options.digest ??= true;
305
+ options.follow ??= 20;
306
+ options.h2 ??= h2;
307
+ options.headers = {
308
+ [HTTP2_HEADER_ACCEPT]: `${_mediatypes.APPLICATION_JSON}, ${_mediatypes.TEXT_PLAIN}, ${_mediatypes.WILDCARD}`,
309
+ [HTTP2_HEADER_ACCEPT_ENCODING]: 'br, deflate, gzip, identity',
310
+ ...Object.entries(options.headers ?? {}).reduce((acc, [key, val]) => (acc[key.toLowerCase()] = val, acc), {}),
311
+ ...(h2 && {
312
+ [HTTP2_HEADER_AUTHORITY]: url.host,
313
+ [HTTP2_HEADER_METHOD]: method,
314
+ [HTTP2_HEADER_PATH]: `${url.pathname}${url.search}`,
315
+ [HTTP2_HEADER_SCHEME]: url.protocol.replace(/\p{Punctuation}/gu, '')
316
+ })
317
+ };
318
+ options.method ??= method;
319
+ options.parse ??= true;
320
+ options.redirect ??= redirects.follow;
321
+
322
+ if (!Object.values(redirects).includes(options.redirect)) {
323
+ options.createConnection?.().destroy();
324
+ throw new TypeError(`Failed to read the 'redirect' property from 'options': The provided value '${options.redirect}' is not a valid enum value.`);
325
+ }
326
+
327
+ options.redirected ??= false;
328
+ options.thenable ??= false;
329
+ return options;
330
+ };
331
+
332
+ exports.preflight = preflight;
285
333
  const redirects = {
286
334
  error: 'error',
287
335
  follow: 'follow',
package/dist/index.js CHANGED
@@ -15,6 +15,8 @@ exports.constants = _http2.constants;
15
15
 
16
16
  var _https = _interopRequireDefault(require("https"));
17
17
 
18
+ var _promises = require("timers/promises");
19
+
18
20
  var _ackn = require("./ackn.js");
19
21
 
20
22
  Object.keys(_ackn).forEach(function (key) {
@@ -77,29 +79,55 @@ const {
77
79
  HTTP2_HEADER_CONTENT_LENGTH,
78
80
  HTTP2_HEADER_CONTENT_TYPE,
79
81
  HTTP2_HEADER_LOCATION,
82
+ HTTP2_HEADER_RETRY_AFTER,
80
83
  HTTP2_HEADER_SET_COOKIE,
81
84
  HTTP2_HEADER_STATUS,
82
85
  HTTP2_METHOD_GET,
83
86
  HTTP2_METHOD_HEAD,
84
87
  HTTP_STATUS_BAD_REQUEST,
85
- HTTP_STATUS_SEE_OTHER
88
+ HTTP_STATUS_MOVED_PERMANENTLY,
89
+ HTTP_STATUS_SEE_OTHER,
90
+ HTTP_STATUS_SERVICE_UNAVAILABLE,
91
+ HTTP_STATUS_TOO_MANY_REQUESTS
86
92
  } = _http2.default.constants;
93
+ const maxRetryAfter = Symbol('maxRetryAfter');
94
+
95
+ const maxRetryAfterError = (interval, options) => new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms.`, options);
96
+
97
+ let defaults = {
98
+ follow: 20,
99
+
100
+ get maxRetryAfter() {
101
+ return this[maxRetryAfter] ?? this.timeout;
102
+ },
103
+
104
+ set maxRetryAfter(value) {
105
+ this[maxRetryAfter] = value;
106
+ },
107
+
108
+ method: HTTP2_METHOD_GET,
109
+ retry: {
110
+ attempts: 0,
111
+ backoffStrategy: 'interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)',
112
+ interval: 1e3,
113
+ retryAfter: true,
114
+ statusCodes: [HTTP_STATUS_TOO_MANY_REQUESTS, HTTP_STATUS_SERVICE_UNAVAILABLE]
115
+ },
116
+ timeout: 3e5
117
+ };
87
118
 
88
119
  async function rekwest(url, options = {}) {
89
120
  url = options.url = new URL(url);
90
121
 
91
122
  if (!options.redirected) {
92
- options = (0, _helpers.merge)(rekwest.defaults, {
93
- follow: 20,
94
- method: HTTP2_METHOD_GET
95
- }, options);
123
+ options = (0, _helpers.merge)(rekwest.defaults, options);
96
124
  }
97
125
 
98
126
  if (options.body && [HTTP2_METHOD_GET, HTTP2_METHOD_HEAD].includes(options.method)) {
99
127
  throw new TypeError(`Request with ${HTTP2_METHOD_GET}/${HTTP2_METHOD_HEAD} method cannot have body.`);
100
128
  }
101
129
 
102
- if (!options.follow) {
130
+ if (options.follow === 0) {
103
131
  throw new _errors.RequestError(`Maximum redirect reached at: ${url.href}`);
104
132
  }
105
133
 
@@ -139,26 +167,22 @@ async function rekwest(url, options = {}) {
139
167
  req = request(url, options);
140
168
  }
141
169
 
142
- req.on('response', res => {
170
+ (0, _helpers.affix)(client, req, options);
171
+ req.once('error', reject);
172
+ req.once('frameError', reject);
173
+ req.once('goaway', reject);
174
+ req.once('response', res => {
175
+ let headers;
176
+
143
177
  if (h2) {
144
- const headers = res;
178
+ headers = res;
145
179
  res = req;
146
- Reflect.defineProperty(res, 'headers', {
147
- enumerable: true,
148
- value: headers
149
- });
150
- Reflect.defineProperty(res, 'httpVersion', {
151
- enumerable: true,
152
- value: `${h2 + 1}.0`
153
- });
154
- Reflect.defineProperty(res, 'statusCode', {
155
- enumerable: true,
156
- value: headers[HTTP2_HEADER_STATUS]
157
- });
158
180
  } else {
159
- res.on('error', reject);
181
+ res.once('error', reject);
160
182
  }
161
183
 
184
+ (0, _helpers.admix)(res, headers, options);
185
+
162
186
  if (cookies !== false && res.headers[HTTP2_HEADER_SET_COOKIE]) {
163
187
  if (_cookies.Cookies.jar.has(url.origin)) {
164
188
  new _cookies.Cookies(res.headers[HTTP2_HEADER_SET_COOKIE]).forEach(function (val, key) {
@@ -195,37 +219,29 @@ async function rekwest(url, options = {}) {
195
219
  }
196
220
 
197
221
  Reflect.set(options, 'redirected', true);
222
+
223
+ if (res.statusCode === HTTP_STATUS_MOVED_PERMANENTLY && res.headers[HTTP2_HEADER_RETRY_AFTER]) {
224
+ let interval = res.headers[HTTP2_HEADER_RETRY_AFTER];
225
+ interval = Number(interval) * 1000 || new Date(interval) - Date.now();
226
+
227
+ if (interval > options.maxRetryAfter) {
228
+ res.emit('error', maxRetryAfterError(interval, {
229
+ cause: (0, _helpers.mixin)(res, options)
230
+ }));
231
+ }
232
+
233
+ return (0, _promises.setTimeout)(interval).then(() => rekwest(options.url, options).then(resolve, reject));
234
+ }
235
+
198
236
  return rekwest(options.url, options).then(resolve, reject);
199
237
  }
200
238
  }
201
239
 
202
- Reflect.defineProperty(res, 'ok', {
203
- enumerable: true,
204
- value: /^2\d{2}$/.test(res.statusCode)
205
- });
206
- Reflect.defineProperty(res, 'redirected', {
207
- enumerable: true,
208
- value: options.redirected
209
- });
210
-
211
240
  if (res.statusCode >= HTTP_STATUS_BAD_REQUEST) {
212
- return reject((0, _helpers.premix)(res, options));
241
+ return reject((0, _helpers.mixin)(res, options));
213
242
  }
214
243
 
215
- resolve((0, _helpers.premix)(res, options));
216
- });
217
- req.on('end', () => {
218
- client?.close();
219
- });
220
- req.on('error', reject);
221
- req.on('frameError', reject);
222
- req.on('goaway', reject);
223
- req.on('timeout', req.destroy);
224
- req.on('trailers', trailers => {
225
- Reflect.defineProperty(req, 'trailers', {
226
- enumerable: true,
227
- value: trailers
228
- });
244
+ resolve((0, _helpers.mixin)(res, options));
229
245
  });
230
246
  (0, _helpers.dispatch)(req, { ...options,
231
247
  body
@@ -241,6 +257,29 @@ async function rekwest(url, options = {}) {
241
257
 
242
258
  return res;
243
259
  } catch (ex) {
260
+ if (options.retry?.attempts && options.retry?.statusCodes.includes(ex.statusCode)) {
261
+ let {
262
+ interval
263
+ } = options.retry;
264
+
265
+ if (options.retry.retryAfter && ex.headers[HTTP2_HEADER_RETRY_AFTER]) {
266
+ interval = ex.headers[HTTP2_HEADER_RETRY_AFTER];
267
+ interval = Number(interval) * 1000 || new Date(interval) - Date.now();
268
+
269
+ if (interval > options.maxRetryAfter) {
270
+ throw maxRetryAfterError(interval, {
271
+ cause: ex
272
+ });
273
+ }
274
+ } else {
275
+ interval = new Function('interval', `return Math.ceil(${options.retry.backoffStrategy});`)(interval);
276
+ }
277
+
278
+ options.retry.attempts--;
279
+ options.retry.interval = interval;
280
+ return (0, _promises.setTimeout)(interval).then(() => rekwest(url, options));
281
+ }
282
+
244
283
  if (digest && !redirected && ex.body) {
245
284
  ex.body = await ex.body();
246
285
  }
@@ -265,18 +304,8 @@ Reflect.defineProperty(rekwest, 'stream', {
265
304
  }, options),
266
305
  redirect: _helpers.redirects.manual
267
306
  });
268
-
269
- if (options.h2) {
270
- const client = _http2.default.connect(url.origin, options);
271
-
272
- const req = client.request(options.headers, options);
273
- req.on('end', () => {
274
- client.close();
275
- });
276
- return req;
277
- }
278
-
279
307
  const {
308
+ h2,
280
309
  url: {
281
310
  protocol
282
311
  }
@@ -284,11 +313,38 @@ Reflect.defineProperty(rekwest, 'stream', {
284
313
  const {
285
314
  request
286
315
  } = protocol === 'http:' ? _http.default : _https.default;
287
- return request(options.url, options);
316
+ let client, req;
317
+
318
+ if (h2) {
319
+ client = _http2.default.connect(url.origin, options);
320
+ req = client.request(options.headers, options);
321
+ } else {
322
+ req = request(options.url, options);
323
+ }
324
+
325
+ (0, _helpers.affix)(client, req, options);
326
+ req.once('response', res => {
327
+ let headers;
328
+
329
+ if (h2) {
330
+ headers = res;
331
+ res = req;
332
+ }
333
+
334
+ (0, _helpers.admix)(res, headers, options);
335
+ });
336
+ return req;
288
337
  }
289
338
  });
290
339
  Reflect.defineProperty(rekwest, 'defaults', {
291
340
  enumerable: true,
292
- value: Object.create(null),
293
- writable: true
341
+
342
+ get() {
343
+ return defaults;
344
+ },
345
+
346
+ set(value) {
347
+ defaults = (0, _helpers.merge)(defaults, value);
348
+ }
349
+
294
350
  });
package/package.json CHANGED
@@ -8,14 +8,14 @@
8
8
  "url": "https://github.com/bricss/rekwest/issues"
9
9
  },
10
10
  "devDependencies": {
11
- "@babel/cli": "^7.16.8",
12
- "@babel/core": "^7.16.12",
13
- "@babel/eslint-parser": "^7.16.5",
11
+ "@babel/cli": "^7.17.6",
12
+ "@babel/core": "^7.17.5",
13
+ "@babel/eslint-parser": "^7.17.0",
14
14
  "@babel/preset-env": "^7.16.11",
15
15
  "c8": "^7.11.0",
16
- "eslint": "^8.8.0",
17
- "eslint-config-ultra-refined": "^2.4.0",
18
- "mocha": "^9.2.0"
16
+ "eslint": "^8.10.0",
17
+ "eslint-config-ultra-refined": "^2.4.1",
18
+ "mocha": "^9.2.1"
19
19
  },
20
20
  "description": "The robust request library that humanity deserves 🌐",
21
21
  "engines": {
@@ -60,5 +60,5 @@
60
60
  "test:bail": "mocha --bail",
61
61
  "test:cover": "c8 --include=src --reporter=lcov --reporter=text npm test"
62
62
  },
63
- "version": "2.4.0"
63
+ "version": "3.0.0"
64
64
  }