rekwest 7.2.3 → 7.2.5

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
@@ -48,10 +48,11 @@ const res = await rekwest(url, {
48
48
  body: { celestial: 'payload' },
49
49
  headers: {
50
50
  [HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
51
- [HTTP2_HEADER_CONTENT_ENCODING]: 'br', // Enables: body encoding
52
- /** [HTTP2_HEADER_CONTENT_TYPE] is undue for
51
+ [HTTP2_HEADER_CONTENT_ENCODING]: 'br', // Enables: body encoding
52
+ /**
53
+ * [HTTP2_HEADER_CONTENT_TYPE] is undue for
53
54
  * Array/Blob/File/FormData/Object/URLSearchParams body types
54
- * and will be set automatically, with an option to override it here
55
+ * and will be set automatically, with an option to override it here.
55
56
  */
56
57
  },
57
58
  method: HTTP2_METHOD_POST,
@@ -82,16 +83,21 @@ const {
82
83
 
83
84
  const blob = new Blob(['bits']);
84
85
  const file = new File(['bits'], 'file.xyz');
85
- const readable = Readable.from('bits');
86
+ const rbl = Readable.from('bits');
87
+ const rds = ReadableStream.from('bits');
86
88
 
87
89
  const fd = new FormData({
88
- aux: Date.now(), // Either [[key, value]] or kv sequenceable
90
+ aux: new Date(), // Either [[key, value]] or kv sequenceable
89
91
  });
90
92
 
91
93
  fd.append('celestial', 'payload');
92
94
  fd.append('blob', blob, 'blob.xyz');
93
95
  fd.append('file', file);
94
- fd.append('readable', readable, 'readable.xyz');
96
+ fd.append('rbl', rbl, 'rbl.xyz');
97
+ fd.append('rds', rds, 'rds.xyz');
98
+ /**
99
+ * Streamable entries are consumed on request submittion.
100
+ */
95
101
 
96
102
  const url = 'https://somewhe.re/somewhat/endpoint';
97
103
 
@@ -99,7 +105,7 @@ const res = await rekwest(url, {
99
105
  body: fd,
100
106
  headers: {
101
107
  [HTTP2_HEADER_AUTHORIZATION]: 'Bearer [token]',
102
- [HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // Enables: body encoding
108
+ [HTTP2_HEADER_CONTENT_ENCODING]: 'zstd', // Enables: body encoding
103
109
  },
104
110
  method: HTTP2_METHOD_POST,
105
111
  });
@@ -113,71 +119,70 @@ console.log(res.body);
113
119
 
114
120
  #### `rekwest(url[, options])`
115
121
 
116
- * `url` **{string | URL}** The URL to send the request to
122
+ * `url` **{string | URL}** The URL to send the request to.
117
123
  * `options` **{Object}**
118
124
  Extends [http(s).RequestOptions](https://nodejs.org/api/https.html#httpsrequesturl-options-callback) along with
119
125
  extra [http2.ClientSessionOptions](https://nodejs.org/api/http2.html#http2connectauthority-options-listener)
120
126
  & [http2.ClientSessionRequestOptions](https://nodejs.org/api/http2.html#clienthttp2sessionrequestheaders-options)
121
127
  and [tls.ConnectionOptions](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)
122
- for HTTP/2 attunes
123
- * `allowDowngrade` **{boolean}** `Default: false` Controls whether `https:` redirects to `http:` are allowed
124
- * `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL
128
+ for HTTP/2 attunes.
129
+ * `allowDowngrade` **{boolean}** `Default: false` Controls whether `https:` redirects to `http:` are allowed.
130
+ * `baseURL` **{string | URL}** The base URL to use in cases where `url` is a relative URL.
125
131
  * `body` **{string | Array | ArrayBuffer | ArrayBufferView | AsyncIterator | Blob | Buffer | DataView | File |
126
132
  FormData | Iterator | Object | Readable | ReadableStream | SharedArrayBuffer | URLSearchParams}** The body to send
127
- with the request
133
+ with the request.
128
134
  * `bufferBody` **{boolean}** `Default: false` Toggles the buffering of the streamable request bodies for redirects and
129
- retries
130
- * `cookies` **{boolean | string[] | Array<[k, v]> | Cookies | Object | URLSearchParams}** `Default: true` The
131
- cookies to add to
132
- the request
133
- * `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache
135
+ retries.
136
+ * `cookies` **{boolean | string | string[] | [k, v][] | Cookies | Object | URLSearchParams}** `Default: true` The
137
+ cookies to add to the request. Manually set `cookie` header to override.
138
+ * `cookiesTTL` **{boolean}** `Default: false` Controls enablement of TTL for the cookies cache.
134
139
  * `credentials` **{include | omit | same-origin}** `Default: same-origin` Controls credentials in case of cross-origin
135
- redirects
136
- * `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `zlib`, `zstd`
137
- * `digest` **{boolean}** `Default: true` Controls whether to read the response stream or add a mixin
138
- * `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `zlib`, `zstd`
139
- * `follow` **{number}** `Default: 20` The number of redirects to follow
140
- * `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol
141
- * `headers` **{Object}** The headers to add to the request
142
- * `params` **{Object}** The search params to add to the `url`
143
- * `parse` **{boolean}** `Default: true` Controls whether to parse response body or return a buffer
144
- * `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows
145
- * `retry` **{Object}** Represents the retry options
146
- * `attempts` **{number}** `Default: 0` The number of retry attempts
140
+ redirects.
141
+ * `decodersOptions` **{Object}** Configures decoders options, e.g.: `brotli`, `zlib`, `zstd`.
142
+ * `digest` **{boolean}** `Default: true` Controls whether to read the response stream or add a mixin.
143
+ * `encodersOptions` **{Object}** Configures encoders options, e.g.: `brotli`, `zlib`, `zstd`.
144
+ * `follow` **{number}** `Default: 20` The number of redirects to follow.
145
+ * `h2` **{boolean}** `Default: false` Forces the use of HTTP/2 protocol.
146
+ * `headers` **{Object}** The headers to add to the request.
147
+ * `params` **{Object}** The search params to add to the `url`.
148
+ * `parse` **{boolean}** `Default: true` Controls whether to parse response body or return a buffer.
149
+ * `redirect` **{error | follow | manual}** `Default: follow` Controls the redirect flows.
150
+ * `retry` **{Object}** Represents the retry options.
151
+ * `attempts` **{number}** `Default: 0` The number of retry attempts.
147
152
  * `backoffStrategy` **{string}** `Default: interval * Math.log(Math.random() * (Math.E * Math.E - Math.E) + Math.E)`
148
153
  The backoff strategy uses a log-uniform algorithm. To fix the interval, set the value to `interval * 1`.
149
154
  * `errorCodes` **{string[]}**
150
155
  `Default: ['ECONNREFUSED', 'ECONNRESET', 'EHOSTDOWN', 'EHOSTUNREACH', 'ENETDOWN', 'ENETUNREACH', 'ENOTFOUND', 'ERR_HTTP2_STREAM_ERROR']`
151
- The list of error codes to retry on
152
- * `interval` **{number}** `Default: 1e3` The initial retry interval
153
- * `maxRetryAfter` **{number}** `Default: 3e5` The maximum `retry-after` limit in milliseconds
154
- * `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness
155
- * `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on
156
- * `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL
157
- * `thenable` **{boolean}** `Default: false` Controls the promise resolutions
158
- * `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination
159
- * `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL
156
+ The list of error codes to retry on.
157
+ * `interval` **{number}** `Default: 1e3` The initial retry interval.
158
+ * `maxRetryAfter` **{number}** `Default: 3e5` The maximum `retry-after` limit in milliseconds.
159
+ * `retryAfter` **{boolean}** `Default: true` Controls `retry-after` header receptiveness.
160
+ * `statusCodes` **{number[]}** `Default: [429, 500, 502, 503, 504]` The list of status codes to retry on.
161
+ * `stripTrailingSlash` **{boolean}** `Default: false` Controls whether to strip trailing slash at the end of the URL.
162
+ * `thenable` **{boolean}** `Default: false` Controls the promise resolutions.
163
+ * `timeout` **{number}** `Default: 3e5` The number of milliseconds a request can take before termination.
164
+ * `trimTrailingSlashes` **{boolean}** `Default: false` Controls whether to trim trailing slashes within the URL.
160
165
  * **Returns:** Promise that resolves to
161
166
  extended [http.IncomingMessage](https://nodejs.org/api/http.html#class-httpincomingmessage)
162
167
  or [http2.ClientHttp2Stream](https://nodejs.org/api/http2.html#class-clienthttp2stream) which is respectively
163
- readable and duplex streams
168
+ readable and duplex streams.
164
169
  * if `digest: true` & `parse: true`
165
- * `body` **{string | Array | Buffer | Object}** The body based on its content type
170
+ * `body` **{string | Array | Buffer | Object}** The body based on its content type.
166
171
  * if `digest: false`
167
- * `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**
168
- * `blob` **{AsyncFunction}** Reads the response and returns **Blob**
169
- * `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`
170
- * `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**
171
- * `json` **{AsyncFunction}** Reads the response and returns **Object**
172
- * `text` **{AsyncFunction}** Reads the response and returns **String**
173
- * `bodyUsed` **{boolean}** Indicates whether the response was read or not
174
- * `cookies` **{undefined | Cookies}** The cookies sent and received with the response
175
- * `headers` **{Object}** The headers received with the response
176
- * `httpVersion` **{string}** Indicates a protocol version negotiated with the server
177
- * `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**)
178
- * `redirected` **{boolean}** Indicates if the response is the result of a redirect
179
- * `statusCode` **{number}** Indicates the status code of the response
180
- * `trailers` **{undefined | Object}** The trailer headers received with the response
172
+ * `arrayBuffer` **{AsyncFunction}** Reads the response and returns **ArrayBuffer**.
173
+ * `blob` **{AsyncFunction}** Reads the response and returns **Blob**.
174
+ * `body` **{AsyncFunction}** Reads the response and returns **Buffer** if `parse: false`.
175
+ * `bytes` **{AsyncFunction}** Reads the response and returns **Uint8Array**.
176
+ * `json` **{AsyncFunction}** Reads the response and returns **Object**.
177
+ * `text` **{AsyncFunction}** Reads the response and returns **String**.
178
+ * `bodyUsed` **{boolean}** Indicates whether the response was read or not.
179
+ * `cookies` **{undefined | Cookies}** The cookies sent and received with the response.
180
+ * `headers` **{Object}** The headers received with the response.
181
+ * `httpVersion` **{string}** Indicates a protocol version negotiated with the server.
182
+ * `ok` **{boolean}** Indicates if the response was successful (statusCode: **200-299**).
183
+ * `redirected` **{boolean}** Indicates if the response is the result of a redirect.
184
+ * `statusCode` **{number}** Indicates the status code of the response.
185
+ * `trailers` **{undefined | Object}** The trailer headers received with the response.
181
186
 
182
187
  ---
183
188
 
@@ -205,7 +210,7 @@ const rk = rekwest.extend({
205
210
  const params = {
206
211
  id: '[uid]',
207
212
  signature: '[code]',
208
- variant: 'A',
213
+ variant: '[any]',
209
214
  };
210
215
  const signal = AbortSignal.timeout(3e4);
211
216
  const url = '/somewhat/endpoint';
@@ -226,9 +231,9 @@ console.log(res.body);
226
231
 
227
232
  The method with limited functionality to use with streams and/or pipes.
228
233
 
229
- * No automata (redirects & retries)
230
- * Pass `h2: true` in options to use HTTP/2 protocol
231
- * Use `ackn({ url: URL })` method in advance to check the available protocols
234
+ * No automata (redirects & retries).
235
+ * Pass `h2: true` in options to use HTTP/2 protocol.
236
+ * Use `ackn({ url: URL })` method in advance to check the available protocols.
232
237
 
233
238
  ```javascript
234
239
  import fs from 'node:fs';
package/dist/ackn.cjs CHANGED
@@ -27,11 +27,11 @@ const ackn = options => new Promise((resolve, reject) => {
27
27
  createConnection() {
28
28
  return socket;
29
29
  },
30
- h2: /h2c?/i.test(alpnProtocol),
30
+ h2: /\bh2\b/i.test(alpnProtocol),
31
31
  protocol: url.protocol
32
32
  });
33
33
  });
34
- socket.on('error', reject);
35
- socket.on('timeout', reject);
34
+ socket.once('error', reject);
35
+ socket.once('timeout', reject);
36
36
  });
37
37
  exports.ackn = ackn;
package/dist/cookies.cjs CHANGED
@@ -3,10 +3,17 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.Cookies = void 0;
6
+ exports.splitCookie = exports.maxCookieSize = exports.maxCookieLifetimeCap = exports.isValidCookie = exports.illegalCookieChars = exports.cookieRex = exports.cookiePairRex = exports.Cookies = void 0;
7
7
  var _utils = require("./utils.cjs");
8
- const lifetimeCap = 3456e7; // 400 days
9
-
8
+ const cookieRex = exports.cookieRex = /^[\w-]+=(?:"[^"]*"|[^\p{Control};]*)(?:;\s*(?:[\w-]+=(?:"[^"]*"|[^\p{Control};]*)|[\w-]+))*$/u;
9
+ const cookiePairRex = exports.cookiePairRex = /(?:[^;"\s]+="[^"]*"|[^;]+)(?=;|$)/g;
10
+ const illegalCookieChars = exports.illegalCookieChars = /\p{Control}/u;
11
+ const isValidCookie = str => str?.constructor === String && cookieRex.test(str);
12
+ exports.isValidCookie = isValidCookie;
13
+ const maxCookieLifetimeCap = exports.maxCookieLifetimeCap = 3456e7; // 400 days
14
+ const maxCookieSize = exports.maxCookieSize = 4096;
15
+ const splitCookie = str => str.match(cookiePairRex).map(str => str.trim());
16
+ exports.splitCookie = splitCookie;
10
17
  class Cookies extends URLSearchParams {
11
18
  static #finalizers = new Set();
12
19
  static jar = new Map();
@@ -27,27 +34,39 @@ class Cookies extends URLSearchParams {
27
34
  } = {
28
35
  cookiesTTL: false
29
36
  }) {
30
- if (Array.isArray(input) && input.every(it => !Array.isArray(it))) {
31
- input = input.map(it => {
32
- if (!cookiesTTL) {
33
- return [it.split(';')[0].trim()];
34
- }
35
- const [cookie, ...attrs] = it.split(';').map(it => it.trim());
36
- const ttl = {};
37
- for (const attr of attrs) {
38
- if (/(?:expires|max-age)=/i.test(attr)) {
39
- const [key, val] = attr.toLowerCase().split('=');
40
- const ms = Number.isFinite(Number.parseInt(val, 10)) ? val * 1e3 : Date.parse(val) - Date.now();
41
- ttl[(0, _utils.toCamelCase)(key)] = Math.min(ms, lifetimeCap);
37
+ if (isValidCookie(input)) {
38
+ input = splitCookie(input);
39
+ }
40
+ const ttlMap = new Map();
41
+ if (Array.isArray(input)) {
42
+ if (input.every(it => isValidCookie(it))) {
43
+ input = input.filter(it => !illegalCookieChars.test(it) && it.length <= maxCookieSize);
44
+ input = input.map(splitCookie).map(([cookie, ...attrs]) => {
45
+ try {
46
+ cookie = cookie.split('=').map(it => decodeURIComponent(it.trim()));
47
+ return cookie;
48
+ } finally {
49
+ if (cookiesTTL) {
50
+ for (const attr of attrs) {
51
+ if (/(?:expires|max-age)=/i.test(attr)) {
52
+ const [key, val] = attr.toLowerCase().split('=');
53
+ let interval = val * 1e3 || Date.parse(val) - Date.now();
54
+ if (interval < 0 || Number.isNaN(interval)) {
55
+ interval = 0;
56
+ }
57
+ ttlMap.set(cookie[0], {
58
+ [(0, _utils.toCamelCase)(key.trim())]: Math.min(interval, maxCookieLifetimeCap)
59
+ });
60
+ }
61
+ }
62
+ }
42
63
  }
43
- }
44
- return [cookie.replace(/\u0022/g, ''), Object.keys(ttl).length ? ttl : null];
45
- });
64
+ });
65
+ }
46
66
  }
47
- super(Array.isArray(input) ? input.map(it => it[0]).join('&') : input);
48
- if (Array.isArray(input) && cookiesTTL) {
49
- for (const [cookie, ttl] of input.filter(it => it[1])) {
50
- const key = cookie.split('=')[0];
67
+ super(input);
68
+ if (ttlMap.size) {
69
+ for (const [key, attrs] of ttlMap) {
51
70
  if (this.#chronometry.has(key)) {
52
71
  clearTimeout(this.#chronometry.get(key));
53
72
  this.#chronometry.delete(key);
@@ -55,9 +74,9 @@ class Cookies extends URLSearchParams {
55
74
  const {
56
75
  expires,
57
76
  maxAge
58
- } = ttl;
59
- for (const ms of [maxAge, expires]) {
60
- if (!Number.isInteger(ms)) {
77
+ } = attrs;
78
+ for (const interval of [maxAge, expires]) {
79
+ if (!Number.isInteger(interval)) {
61
80
  continue;
62
81
  }
63
82
  const ref = new WeakRef(this);
@@ -67,7 +86,7 @@ class Cookies extends URLSearchParams {
67
86
  ctx.#chronometry.delete(key);
68
87
  ctx.delete(key);
69
88
  }
70
- }, Math.max(ms, 0));
89
+ }, Math.max(interval, 0));
71
90
  this.constructor.#register(this, tid);
72
91
  this.#chronometry.set(key, tid);
73
92
  break;
package/dist/formdata.cjs CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.FormData = void 0;
6
+ exports.parseFormData = exports.isFormData = exports.fdToAsyncIterable = exports.FormData = void 0;
7
7
  var _nodeBuffer = require("node:buffer");
8
8
  var _nodeCrypto = require("node:crypto");
9
9
  var _nodeHttp = _interopRequireDefault(require("node:http2"));
@@ -16,40 +16,24 @@ const {
16
16
  HTTP2_HEADER_CONTENT_TYPE
17
17
  } = _nodeHttp.default.constants;
18
18
  class FormData {
19
- static actuate(fd) {
20
- const boundary = (0, _nodeCrypto.randomBytes)(24).toString('hex');
21
- const contentType = `${_mediatypes.MULTIPART_FORM_DATA}; boundary=${boundary}`;
22
- const prefix = `--${boundary}${CRLF}${HTTP2_HEADER_CONTENT_DISPOSITION}: form-data`;
23
- const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
24
- const redress = str => str.replace(/\r?\n|\r/g, CRLF);
25
- return {
26
- contentType,
27
- async *[Symbol.asyncIterator]() {
28
- const encoder = new TextEncoder();
29
- for (const [name, val] of fd) {
30
- if (val.constructor === String) {
31
- yield encoder.encode(`${prefix}; name="${escape(redress(name))}"${CRLF.repeat(2)}${redress(val)}${CRLF}`);
32
- } else {
33
- yield encoder.encode(`${prefix}; name="${escape(redress(name))}"${val.name ? `; filename="${escape(val.name)}"` : ''}${CRLF}${HTTP2_HEADER_CONTENT_TYPE}: ${val.type || _mediatypes.APPLICATION_OCTET_STREAM}${CRLF.repeat(2)}`);
34
- yield* (0, _utils.tap)(val);
35
- yield new Uint8Array([13, 10]);
36
- }
37
- }
38
- yield encoder.encode(`--${boundary}--`);
19
+ static #ensureArgs(args, expect, method) {
20
+ if (args.length < expect) {
21
+ throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expect} arguments required, but only ${args.length} present`);
22
+ }
23
+ if (method === 'forEach') {
24
+ if (args[0]?.constructor !== Function) {
25
+ throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expect} is not of type 'Function'`);
39
26
  }
40
- };
41
- }
42
- static alike(val) {
43
- return FormData.name === val?.[Symbol.toStringTag];
27
+ }
44
28
  }
45
- static #enfoldEntry(name, value, filename) {
29
+ static #formEntry(name, value, filename) {
46
30
  name = String(name).toWellFormed();
47
31
  filename &&= String(filename).toWellFormed();
48
- if ((0, _utils.isFileLike)(value)) {
49
- filename ??= value.name || 'blob';
32
+ if ((0, _utils.isBlobLike)(value)) {
33
+ filename ??= String(value.name ?? 'blob').toWellFormed();
50
34
  value = new _nodeBuffer.File([value], filename, value);
51
- } else if (this.#ensureInstance(value)) {
52
- value.name = filename;
35
+ } else if ((0, _utils.isPipeStream)(value) || (0, _utils.isReadableStream)(value)) {
36
+ value.name = filename ?? 'blob';
53
37
  } else {
54
38
  value = String(value).toWellFormed();
55
39
  }
@@ -58,9 +42,6 @@ class FormData {
58
42
  value
59
43
  };
60
44
  }
61
- static #ensureInstance(val) {
62
- return (0, _utils.isFileLike)(val) || Object(val) === val && Reflect.has(val, Symbol.asyncIterator);
63
- }
64
45
  #entries = [];
65
46
  get [Symbol.toStringTag]() {
66
47
  return this.constructor.name;
@@ -70,10 +51,10 @@ class FormData {
70
51
  if (Array.isArray(input)) {
71
52
  if (!input.every(it => Array.isArray(it))) {
72
53
  throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': The provided value cannot be converted to a sequence`);
73
- } else if (!input.every(it => it.length === 2)) {
54
+ }
55
+ if (!input.every(it => it.length === 2)) {
74
56
  throw new TypeError(`Failed to construct '${this[Symbol.toStringTag]}': Sequence initializer must only contain pair elements`);
75
57
  }
76
- input = Array.from(input);
77
58
  } else if (!Reflect.has(input, Symbol.iterator)) {
78
59
  input = Object.entries(input);
79
60
  }
@@ -82,35 +63,20 @@ class FormData {
82
63
  }
83
64
  }
84
65
  }
85
- #ensureArgs(args, expected, method) {
86
- if (args.length < expected) {
87
- throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': ${expected} arguments required, but only ${args.length} present`);
88
- }
89
- if (['append', 'set'].includes(method)) {
90
- if (args.length === 3 && !this.constructor.#ensureInstance(args[1])) {
91
- throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Blob'`);
92
- }
93
- }
94
- if (method === 'forEach') {
95
- if (args[0]?.constructor !== Function) {
96
- throw new TypeError(`Failed to execute '${method}' on '${this[Symbol.toStringTag]}': parameter ${expected} is not of type 'Function'`);
97
- }
98
- }
99
- }
100
66
  append(...args) {
101
67
  (0, _utils.brandCheck)(this, FormData);
102
- this.#ensureArgs(args, 2, 'append');
103
- this.#entries.push(this.constructor.#enfoldEntry(...args));
68
+ this.constructor.#ensureArgs(args, 2, this.append.name);
69
+ this.#entries.push(this.constructor.#formEntry(...args));
104
70
  }
105
71
  delete(...args) {
106
72
  (0, _utils.brandCheck)(this, FormData);
107
- this.#ensureArgs(args, 1, 'delete');
73
+ this.constructor.#ensureArgs(args, 1, this.delete.name);
108
74
  const name = String(args[0]).toWellFormed();
109
75
  this.#entries = this.#entries.filter(it => it.name !== name);
110
76
  }
111
77
  forEach(...args) {
112
78
  (0, _utils.brandCheck)(this, FormData);
113
- this.#ensureArgs(args, 1, 'forEach');
79
+ this.constructor.#ensureArgs(args, 1, this.forEach.name);
114
80
  const [callback, thisArg] = args;
115
81
  for (const entry of this) {
116
82
  Reflect.apply(callback, thisArg, [...entry.reverse(), this]);
@@ -118,26 +84,26 @@ class FormData {
118
84
  }
119
85
  get(...args) {
120
86
  (0, _utils.brandCheck)(this, FormData);
121
- this.#ensureArgs(args, 1, 'get');
87
+ this.constructor.#ensureArgs(args, 1, this.get.name);
122
88
  const name = String(args[0]).toWellFormed();
123
89
  return this.#entries.find(it => it.name === name)?.value ?? null;
124
90
  }
125
91
  getAll(...args) {
126
92
  (0, _utils.brandCheck)(this, FormData);
127
- this.#ensureArgs(args, 1, 'getAll');
93
+ this.constructor.#ensureArgs(args, 1, this.getAll.name);
128
94
  const name = String(args[0]).toWellFormed();
129
95
  return this.#entries.filter(it => it.name === name).map(it => it.value);
130
96
  }
131
97
  has(...args) {
132
98
  (0, _utils.brandCheck)(this, FormData);
133
- this.#ensureArgs(args, 1, 'has');
99
+ this.constructor.#ensureArgs(args, 1, this.has.name);
134
100
  const name = String(args[0]).toWellFormed();
135
101
  return !!this.#entries.find(it => it.name === name);
136
102
  }
137
103
  set(...args) {
138
104
  (0, _utils.brandCheck)(this, FormData);
139
- this.#ensureArgs(args, 2, 'set');
140
- const entry = this.constructor.#enfoldEntry(...args);
105
+ this.constructor.#ensureArgs(args, 2, this.set.name);
106
+ const entry = this.constructor.#formEntry(...args);
141
107
  const idx = this.#entries.findIndex(it => it.name === entry.name);
142
108
  if (idx !== -1) {
143
109
  this.#entries.splice(idx, 1, entry);
@@ -171,4 +137,37 @@ class FormData {
171
137
  return this.entries();
172
138
  }
173
139
  }
174
- exports.FormData = FormData;
140
+ exports.FormData = FormData;
141
+ const fdToAsyncIterable = fd => {
142
+ const boundary = (0, _nodeCrypto.randomBytes)(32).toString('hex');
143
+ const contentType = `${_mediatypes.MULTIPART_FORM_DATA}; boundary=${boundary}`;
144
+ const prefix = `--${boundary}${CRLF}${HTTP2_HEADER_CONTENT_DISPOSITION}: form-data`;
145
+ const escape = str => str.replace(/\n/g, '%0A').replace(/\r/g, '%0D').replace(/"/g, '%22');
146
+ const normalize = str => str.replace(/\r?\n|\r/g, CRLF);
147
+ return {
148
+ contentType,
149
+ async *[Symbol.asyncIterator]() {
150
+ const encoder = new TextEncoder();
151
+ for (const [name, val] of fd) {
152
+ if (val.constructor === String) {
153
+ yield encoder.encode(`${prefix}; name="${escape(normalize(name))}"${CRLF.repeat(2)}${normalize(val)}${CRLF}`);
154
+ } else {
155
+ yield encoder.encode(`${prefix}; name="${escape(normalize(name))}"${val.name ? `; filename="${escape(val.name)}"` : ''}${CRLF}${HTTP2_HEADER_CONTENT_TYPE}: ${val.type || _mediatypes.APPLICATION_OCTET_STREAM}${CRLF.repeat(2)}`);
156
+ yield* (0, _utils.tap)(val);
157
+ yield new Uint8Array([13, 10]);
158
+ }
159
+ }
160
+ yield encoder.encode(`--${boundary}--${CRLF}`);
161
+ }
162
+ };
163
+ };
164
+ exports.fdToAsyncIterable = fdToAsyncIterable;
165
+ const isFormData = val => FormData.name === val?.[Symbol.toStringTag];
166
+ exports.isFormData = isFormData;
167
+ const parseFormData = str => {
168
+ const rex = /^-+[^\r\n]+\r?\ncontent-disposition:\s*form-data;\s*name="(?<name>[^"]+)"(?:;\s*filename="(?<filename>[^"]+)")?(?:\r?\n[^\r\n:]+:[^\r\n]*)*\r?\n\r?\n(?<content>.*?)(?=\r?\n-+[^\r\n]+)/gims;
169
+ return [...str.matchAll(rex)].map(({
170
+ groups
171
+ }) => structuredClone(groups));
172
+ };
173
+ exports.parseFormData = parseFormData;
@@ -56,7 +56,7 @@ const preflight = options => {
56
56
  } else {
57
57
  cookie &&= _cookies.Cookies.jar.get(url.origin);
58
58
  }
59
- options.headers = {
59
+ headers = {
60
60
  ...(cookie && {
61
61
  [HTTP2_HEADER_COOKIE]: cookie
62
62
  }),
package/dist/retries.cjs CHANGED
@@ -35,7 +35,7 @@ const retries = (err, options) => {
35
35
  } = retry;
36
36
  if (retry.retryAfter && err.headers?.[HTTP2_HEADER_RETRY_AFTER]) {
37
37
  interval = err.headers[HTTP2_HEADER_RETRY_AFTER];
38
- interval = Number.isFinite(Number.parseInt(interval, 10)) ? interval * 1e3 : new Date(interval) - Date.now();
38
+ interval = interval * 1e3 || Date.parse(interval) - Date.now();
39
39
  if (interval > retry.maxRetryAfter) {
40
40
  throw new _errors.RequestError(`Maximum '${HTTP2_HEADER_RETRY_AFTER}' limit exceeded: ${interval} ms`, {
41
41
  cause: err
@@ -44,7 +44,7 @@ const retries = (err, options) => {
44
44
  } else {
45
45
  interval = new Function('interval', `return Math.ceil(${retry.backoffStrategy});`)(interval);
46
46
  }
47
- if (interval < 0) {
47
+ if (interval < 0 || Number.isNaN(interval)) {
48
48
  interval = 0;
49
49
  }
50
50
  retry.attempts--;
package/dist/transfer.cjs CHANGED
@@ -53,11 +53,9 @@ const transfer = async options => {
53
53
  } = url.protocol === 'http:' ? _nodeHttp.default : _nodeHttps.default;
54
54
  req = request(url, options);
55
55
  }
56
- (0, _utils.snoop)(client, req, options);
57
- req.once('aborted', reject);
58
- req.once('error', reject);
59
- req.once('frameError', reject);
60
- req.once('goaway', reject);
56
+ (0, _utils.snoop)(client, req, options, {
57
+ reject
58
+ });
61
59
  req.once('response', res => (0, _postflight.postflight)(req, res, options, {
62
60
  reject,
63
61
  resolve
@@ -28,7 +28,7 @@ const transform = async options => {
28
28
  }
29
29
  if (!Buffer.isBuffer(body)) {
30
30
  switch (true) {
31
- case (0, _utils.isFileLike)(body):
31
+ case (0, _utils.isBlobLike)(body):
32
32
  {
33
33
  headers = {
34
34
  [HTTP2_HEADER_CONTENT_LENGTH]: body.size,
@@ -37,9 +37,9 @@ const transform = async options => {
37
37
  body = body.stream();
38
38
  break;
39
39
  }
40
- case _formdata.FormData.alike(body):
40
+ case (0, _formdata.isFormData)(body):
41
41
  {
42
- body = _formdata.FormData.actuate(body);
42
+ body = (0, _formdata.fdToAsyncIterable)(body);
43
43
  headers = {
44
44
  [HTTP2_HEADER_CONTENT_TYPE]: body.contentType
45
45
  };